0
0
mirror of https://gitlab.nic.cz/labs/bird.git synced 2024-11-14 15:18:44 +00:00
bird/flock/hypervisor.c
2024-10-10 12:26:59 +02:00

500 lines
10 KiB
C

#include "lib/birdlib.h"
#include "lib/cbor.h"
#include "lib/hash.h"
#include "lib/io-loop.h"
#include "lib/resource.h"
#include "lib/socket.h"
#include "flock/flock.h"
#include <stdlib.h>
#include <sys/socket.h>
/**
* Main control socket
**/
static struct birdloop *hcs_loop;
static pool *hcs_pool;
OBSREF(struct shutdown_placeholder) hcs_shutdown_placeholder;
static int
hcs_rx(sock *s, uint size)
{
s64 sz = hcs_parse(s->data, s->rbuf, size);
if (sz < 0)
{
log(L_INFO "CLI parser error at position %ld: %s", -sz-1, hcs_error(s->data));
sk_close(s);
return 0; /* Must return 0 when closed */
}
if (!hcs_complete(s->data))
{
ASSERT_DIE(sz == size);
return 1;
}
log(L_INFO "Parsed command.");
/* TODO do something more */
if (sz < size)
memmove(s->rbuf, s->rbuf + sz, size - sz);
if (!s->rx_hook)
return (sz == size);
hcs_parser_cleanup(s->data);
s->data = hcs_parser_init(s);
return (sz < size) ? hcs_rx(s, size - sz) : 1;
}
static void
hcs_err(sock *s, int err)
{
log(L_INFO "CLI dropped: %s", strerror(err));
hcs_parser_cleanup(s->data);
sk_close(s);
}
static int
hcs_connect(sock *s, uint size UNUSED)
{
log(L_INFO "CLI connected: %p", s);
s->rx_hook = hcs_rx;
s->err_hook = hcs_err;
s->data = hcs_parser_init(s);
return 1;
}
static void
hcs_connect_err(sock *s UNUSED, int err)
{
ASSERT_DIE(err);
log(L_INFO "Failed to accept CLI connection: %s", strerror(err));
}
static void
hcs_stopped(void *data)
{
ASSERT_DIE(data == hcs_loop);
hcs_pool = NULL;
hcs_loop = NULL;
OBSREF_CLEAR(hcs_shutdown_placeholder);
unlink(flock_config.control_socket_path);
}
static void
hcs_shutdown(void *_data UNUSED)
{
birdloop_stop(hcs_loop, hcs_stopped, hcs_loop);
}
void
hypervisor_control_socket(void)
{
struct birdloop *loop = hcs_loop = birdloop_new(&root_pool, DOMAIN_ORDER(control), 0, "Control socket");
birdloop_enter(loop);
pool *p = hcs_pool = rp_new(birdloop_pool(loop), birdloop_domain(loop), "Control socket pool");
sock *s = sk_new(p);
s->type = SK_UNIX_PASSIVE;
s->rx_hook = hcs_connect;
s->err_hook = hcs_connect_err;
s->rbsize = 1024;
s->tbsize = 1024;
if (sk_open_unix(s, loop, flock_config.control_socket_path) < 0)
die("Can't create control socket %s: %m", flock_config.control_socket_path);
ev_send(&shutdown_event_list, ev_new_init(p, hcs_shutdown, NULL));
birdloop_leave(loop);
OBSREF_SET(hcs_shutdown_placeholder, &shutdown_placeholder);
}
/**
* Exposed process' communication structure
**/
static struct hypervisor_exposed {
pool *p;
sock *s;
struct birdloop *loop;
const char *port_name;
sock *port_sreq;
} he;
/**
* Exposed process' parent side (requestor)
**/
static void hexp_received_telnet(void *);
struct hexp_received_telnet {
event e;
int fd;
u16 port;
};
static int
hypervisor_telnet_connected(sock *sk, uint size UNUSED)
{
int fd = accept(sk->fd, NULL, 0);
if (fd < 0)
{
if (errno == EAGAIN)
return 1;
log(L_ERR "failed to accept telnet connection: %m");
return 0;
}
int e = fork();
if (e < 0)
{
log(L_ERR "failed to fork: %m");
return 0;
}
if (e)
{
log(L_INFO "telnet connected");
close(fd);
sk_close(sk);
return 1;
}
close(0);
close(1);
close(2);
dup2(fd, 0);
dup2(fd, 1);
e = execl("/usr/sbin/telnetd", "telnetd", "-E", "/bin/bash", NULL);
log(L_ERR "failed to execl: %m");
exit(42);
}
static int
hypervisor_exposed_parent_rx(sock *sk, uint size)
{
if ((size != 5) || (sk->rxfd < 0))
{
log(L_ERR "Exposed parent RX %d bytes, fd %d, what the hell", size, sk->rxfd);
sk_close(sk);
ev_send_loop(&main_birdloop, &poweroff_event);
return 0;
}
ASSERT_DIE(sk->rbuf[0] == 0xa1);
ASSERT_DIE(sk->rbuf[1] == 0x21);
ASSERT_DIE(sk->rbuf[2] == 0x19);
u16 port = ntohs(*((u16 *) &sk->rbuf[3]));
log(L_INFO "RX %d bytes, fd %d, port %u", size, sk->rxfd, port);
struct hexp_received_telnet *hrt = mb_allocz(he.p, sizeof *hrt);
*hrt = (struct hexp_received_telnet) {
.e = {
.hook = hexp_received_telnet,
.data = hrt,
},
.port = port,
.fd = sk->rxfd,
};
ev_send_loop(hcs_loop, &hrt->e);
sk->rxfd = -1;
return 0;
}
static void
hypervisor_exposed_parent_err(sock *sk, int e UNUSED)
{
sk_close(sk);
}
/**
* Exposed process' child side (executor)
**/
static int
hypervisor_exposed_child_rx(sock *sk, uint size)
{
if (size != 3)
{
log(L_ERR "Got something strange: %d, %m", size);
abort();
sk_close(sk);
return 0;
}
/* Only one thing is actually supported for now: opening a listening socket */
int sfd = socket(AF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (sfd < 0)
{
log(L_ERR "Failed to socket(): %m");
return 0;
}
while (1)
{
u32 r = (random_u32() % (32768-1024) + 1024);
union {
struct sockaddr_in6 sin;
struct sockaddr a;
} sin = {
.sin = {
.sin6_family = AF_INET6,
.sin6_port = htons(r),
.sin6_addr.s6_addr[15] = 1,
},
};
int e = bind(sfd, &sin.a, sizeof sin);
if (e < 0)
if (errno == EADDRINUSE)
{
log(L_INFO "Tried to bind to %u but already in use", r);
continue;
} else {
log(L_ERR "Failed to bind to %u: %m", r);
close(sfd);
return 0;
}
e = listen(sfd, 10);
if (e < 0)
{
log(L_ERR "Failed to listen(): %m", e);
close(sfd);
return 0;
}
log(L_INFO "SUCCESS");
sk->txfd = sfd;
linpool *lp = lp_new(sk->pool);
struct cbor_writer *cw = cbor_init(sk->tbuf, sk->tbsize, lp);
cbor_open_block_with_length(cw, 1);
cbor_add_int(cw, -2);
cbor_add_int(cw, r);
e = sk_send(sk, cw->pt);
if (e < 0)
log(L_ERR "Failed to send socket: %m");
close(sfd);
return 0;
}
}
static void
hypervisor_exposed_child_err(sock *sk, int e)
{
if (e == 0)
log(L_INFO "Exposed child exiting OK");
else
log(L_ERR "Exposed child control socket failure: %s", strerror(e));
sk_close(sk);
exit(!!e);
}
/**
* Common init code
*/
void
hypervisor_exposed_fork(void)
{
int fds[2], e;
/* create socketpair before forking to do communication */
e = socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds);
if (e < 0)
die("Failed to create internal socketpair: %m");
e = fork();
if (e < 0)
die("Failed to fork exposed: %m");
if (!e) this_thread_id |= 0xe000;
/* Create the communication channel (this runs twice!) */
he.loop = birdloop_new(&root_pool, DOMAIN_ORDER(proto), 0, "Exposed interlink");
birdloop_enter(he.loop);
he.p = rp_new(birdloop_pool(he.loop), birdloop_domain(he.loop), "Exposed interlink pool");
he.s = sk_new(he.p);
he.s->type = SK_MAGIC; /* because we already have the fd */
/* Set the hooks and fds according to the side we are at */
he.s->rx_hook = e ? hypervisor_exposed_parent_rx : hypervisor_exposed_child_rx;
he.s->err_hook = e ? hypervisor_exposed_parent_err : hypervisor_exposed_child_err;
he.s->fd = fds[!!e];
he.s->flags = e ? SKF_FD_RX : SKF_FD_TX;
close(fds[!e]);
if (sk_open(he.s, he.loop) < 0)
bug("Exposed parent: sk_open failed");
sk_set_rbsize(he.s, 128);
sk_set_tbsize(he.s, 128);
he.s->type = SK_UNIX_MSG; /* now we can reveal who we are */
birdloop_leave(he.loop);
/* Now there is a loop both in child and parent, prepared to read the socket.
* There is only one difference. Whereas the parent has to continue its run
* to do other duties, the child is stuck here forever. */
if (e)
return;
/**
* Child only
**/
/* Run worker threads */
struct thread_config tc = {};
bird_thread_commit(&tc);
/* Wait for Godot */
birdloop_minimalist_main();
}
void
hexp_cleanup_after_fork(void)
{
birdloop_enter(he.loop);
rp_free(he.p);
rem_node((node *) he.loop); /* FIXME: this is terrible but i'm lazy now */
birdloop_leave(he.loop);
birdloop_free(he.loop);
}
/**
* Hypervisor's mapping between external ports and names
*/
static void
hexp_sock_err(sock *s, int err UNUSED)
{
ASSERT_DIE(s == he.port_sreq);
he.port_name = NULL;
he.port_sreq = NULL;
}
void
hexp_get_telnet(sock *s, const char *name)
{
ASSERT_DIE(!he.port_name);
he.port_name = name ?: "";
he.port_sreq = s;
uint8_t buf[64];
linpool *lp = lp_new(s->pool);
struct cbor_writer *cw = cbor_init(buf, sizeof buf, lp);
cbor_open_block_with_length(cw, 1);
cbor_add_int(cw, 1);
cw->cbor[cw->pt++] = 0xf6;
int e = write(he.s->fd, buf, cw->pt);
if (e != cw->pt)
bug("write error handling not implemented, got %d (%m)", e);
rfree(lp);
s->err_paused = hexp_sock_err;
sk_pause_rx(s->loop, s);
}
static void hexp_received_telnet(void *_data)
{
struct hexp_received_telnet *hrt = _data;
ASSERT_DIE(he.port_name);
const char *name = he.port_name;
he.port_name = NULL;
sock *s = he.port_sreq;
he.port_sreq = NULL;
if (name[0])
{
/* Transferring the received listening socket to the container */
int fd = container_ctl_fd(name);
/* TODO: unduplicate this code */
byte outbuf[128];
linpool *lp = lp_new(hcs_pool);
struct cbor_writer *cw = cbor_init(outbuf, sizeof outbuf, lp);
cbor_open_block_with_length(cw, 1);
cbor_add_int(cw, -2);
write_item(cw, 7, 22);
struct iovec v = {
.iov_base = outbuf,
.iov_len = cw->pt,
};
byte cbuf[CMSG_SPACE(sizeof hrt->fd)];
struct msghdr m = {
.msg_iov = &v,
.msg_iovlen = 1,
.msg_control = &cbuf,
.msg_controllen = sizeof cbuf,
};
struct cmsghdr *c = CMSG_FIRSTHDR(&m);
c->cmsg_level = SOL_SOCKET;
c->cmsg_type = SCM_RIGHTS;
c->cmsg_len = CMSG_LEN(sizeof hrt->fd);
memcpy(CMSG_DATA(c), &hrt->fd, sizeof hrt->fd);
int e = sendmsg(fd, &m, 0);
if (e < 0)
log(L_ERR "Failed to send socket: %m");
close(hrt->fd);
}
else
{
/* Opening listener here */
sock *skl = sk_new(hcs_pool);
skl->type = SK_MAGIC;
skl->rx_hook = hypervisor_telnet_connected;
skl->data = skl;
skl->fd = hrt->fd;
if (sk_open(skl, hcs_loop) < 0)
bug("Telnet listener: sk_open failed");
}
if (s)
{
linpool *lp = lp_new(hcs_pool);
struct cbor_writer *cw = cbor_init(s->tbuf, s->tbsize, lp);
cbor_open_block_with_length(cw, 1);
cbor_add_int(cw, -2);
cbor_add_int(cw, hrt->port);
sk_send(s, cw->pt);
sk_resume_rx(hcs_loop, s);
hcs_parser_cleanup(s->data);
s->data = hcs_parser_init(s);
rfree(lp);
}
birdloop_enter(he.loop);
mb_free(hrt);
birdloop_leave(he.loop);
}