/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2023, 2025 Mark Johnston <markj@FreeBSD.org>
 *
 * This software was developed by the University of Cambridge Computer
 * Laboratory (Department of Computer Science and Technology) under Innovate
 * UK project 105694, "Digital Security by Design (DSbD) Technology Platform
 * Prototype".
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * The slirp backend enables unprivileged userspace networking via libslirp,
 * which must be installed on the host system via pkg or the ports tree.
 * libslirp.so is dlopen()ed into a helper process with which this backend
 * communicates.
 *
 * Packets received from the guest (i.e., transmitted by the frontend, such as a
 * virtio NIC device model) are injected into the slirp backend via slirp_send(),
 * which sends the packet to the helper process.
 *
 * Packets to be transmitted to the guest (i.e., inserted into the frontend's
 * receive buffers) are buffered in a per-interface socket pair and read by the
 * mevent loop.  Sockets instantiated by libslirp are monitored by a thread
 * which uses poll() and slirp_pollfds_poll() to drive libslirp events; this
 * thread also handles timeout events from the libslirp context.
 */

#include <sys/socket.h>
#include <sys/wait.h>

#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <spawn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "config.h"
#include "debug.h"
#include "mevent.h"
#include "net_utils.h"
#include "net_backends.h"
#include "net_backends_priv.h"

#define	DEFAULT_MTU	2048

struct slirp_priv {
	int s;
	pid_t helper;
	struct mevent *mevp;
	size_t mtu;
	uint8_t *buf;
};

extern char **environ;

static int
slirp_init(struct net_backend *be, const char *devname __unused,
    nvlist_t *nvl, net_be_rxeof_t cb, void *param)
{
	struct slirp_priv *priv = NET_BE_PRIV(be);
	nvlist_t *config;
	posix_spawn_file_actions_t fa;
	pid_t child;
	const char **argv;
	char sockname[32];
	int error, s[2];
	const char *mtu_value;
	size_t mtu;

	if (socketpair(PF_LOCAL, SOCK_SEQPACKET | SOCK_NONBLOCK, 0, s) != 0) {
		EPRINTLN("socketpair");
		return (-1);
	}

	/*
	 * The child will exit once its connection goes away, so make sure only
	 * one end is inherited by the child.
	 */
	if (posix_spawn_file_actions_init(&fa) != 0) {
		EPRINTLN("posix_spawn_file_actions_init");
		goto err;
	}
	if (posix_spawn_file_actions_addclose(&fa, s[0]) != 0) {
		EPRINTLN("posix_spawn_file_actions_addclose");
		posix_spawn_file_actions_destroy(&fa);
		goto err;
	}

	(void)snprintf(sockname, sizeof(sockname), "%d", s[1]);
	argv = (const char *[]){
	    "/usr/libexec/bhyve-slirp-helper", "-S", sockname, NULL
	};
	error = posix_spawn(&child, "/usr/libexec/bhyve-slirp-helper",
	    &fa, NULL, __DECONST(char **, argv), environ);
	posix_spawn_file_actions_destroy(&fa);
	if (error != 0) {
		EPRINTLN("posix_spawn(bhyve-slirp-helper): %s",
		    strerror(error));
		goto err;
	}

	config = nvlist_clone(nvl);
	if (config == NULL) {
		EPRINTLN("nvlist_clone");
		goto err;
	}

	mtu_value = get_config_value_node(config, "mtu");
	if (mtu_value != NULL) {
		if (net_parsemtu(mtu_value, &mtu)) {
		    	EPRINTLN("Could not parse MTU");
		    	goto err;
		}
	} else {
		mtu = DEFAULT_MTU;
	}
	nvlist_add_number(config, "mtui", mtu);

	priv->mtu = mtu;
	priv->buf = malloc(mtu);
	if (priv->buf == NULL) {
		EPRINTLN("Could not allocate buffer");
		goto err;
	}

	nvlist_add_string(config, "vmname", get_config_value("name"));
	error = nvlist_send(s[0], config);
	nvlist_destroy(config);
	if (error != 0) {
		EPRINTLN("nvlist_send");
		goto err;
	}

	be->fd = s[0];
	priv->mevp = mevent_add_disabled(be->fd, EVF_READ, cb, param);
	if (priv->mevp == NULL) {
		EPRINTLN("Could not register event");
		goto err;
	}

	priv->helper = child;
	priv->s = s[0];
	(void)close(s[1]);

	return (0);

err:
	free(priv->buf);
	(void)close(s[0]);
	(void)close(s[1]);
	return (-1);
}

static ssize_t
slirp_send(struct net_backend *be, const struct iovec *iov, int iovcnt)
{
	struct slirp_priv *priv = NET_BE_PRIV(be);
	struct msghdr hdr;

	memset(&hdr, 0, sizeof(hdr));
	hdr.msg_iov = __DECONST(struct iovec *, iov);
	hdr.msg_iovlen = iovcnt;
	return (sendmsg(priv->s, &hdr, MSG_EOR));
}

static void
slirp_cleanup(struct net_backend *be)
{
	struct slirp_priv *priv = NET_BE_PRIV(be);

	free(priv->buf);

	if (priv->helper > 0) {
		int status;

		if (kill(priv->helper, SIGKILL) != 0) {
			EPRINTLN("kill(bhyve-slirp-helper): %s",
			    strerror(errno));
			return;
		}
		(void)waitpid(priv->helper, &status, 0);
	}
}

static ssize_t
slirp_peek_recvlen(struct net_backend *be)
{
	struct slirp_priv *priv = NET_BE_PRIV(be);
	ssize_t n;

	/*
	 * Copying into the buffer is totally unnecessary, but we don't
	 * implement MSG_TRUNC for SEQPACKET sockets.
	 */
	n = recv(priv->s, priv->buf, priv->mtu, MSG_PEEK | MSG_DONTWAIT);
	if (n < 0)
		return (errno == EWOULDBLOCK ? 0 : -1);
	return (n);
}

static ssize_t
slirp_recv(struct net_backend *be, const struct iovec *iov, int iovcnt)
{
	struct slirp_priv *priv = NET_BE_PRIV(be);
	struct msghdr hdr;
	ssize_t n;

	hdr.msg_name = NULL;
	hdr.msg_namelen = 0;
	hdr.msg_iov = __DECONST(struct iovec *, iov);
	hdr.msg_iovlen = iovcnt;
	hdr.msg_control = NULL;
	hdr.msg_controllen = 0;
	hdr.msg_flags = 0;
	n = recvmsg(priv->s, &hdr, MSG_DONTWAIT);
	if (n < 0) {
		if (errno == EWOULDBLOCK)
			return (0);
		return (-1);
	}
	assert((size_t)n <= priv->mtu);
	return (n);
}

static void
slirp_recv_enable(struct net_backend *be)
{
	struct slirp_priv *priv = NET_BE_PRIV(be);

	mevent_enable(priv->mevp);
}

static void
slirp_recv_disable(struct net_backend *be)
{
	struct slirp_priv *priv = NET_BE_PRIV(be);

	mevent_disable(priv->mevp);
}

static uint64_t
slirp_get_cap(struct net_backend *be __unused)
{
	return (0);
}

static int
slirp_set_cap(struct net_backend *be __unused, uint64_t features __unused,
    unsigned int vnet_hdr_len __unused)
{
	return ((features || vnet_hdr_len) ? -1 : 0);
}

static struct net_backend slirp_backend = {
	.prefix = "slirp",
	.priv_size = sizeof(struct slirp_priv),
	.init = slirp_init,
	.cleanup = slirp_cleanup,
	.send = slirp_send,
	.peek_recvlen = slirp_peek_recvlen,
	.recv = slirp_recv,
	.recv_enable = slirp_recv_enable,
	.recv_disable = slirp_recv_disable,
	.get_cap = slirp_get_cap,
	.set_cap = slirp_set_cap,
};

DATA_SET(net_backend_set, slirp_backend);
