clixon/lib/src/clixon_netns.c
2022-10-27 14:21:17 +02:00

343 lines
9.6 KiB
C

/*
*
* Network namespace code
* @thanks Anders Franzén, especially get_sock() and send_sock() functions
*
* fork,
* child:
* switch to ns,
* create sock,
* bind to address,
* sendmsg sock back to parent
* parent:
* readmsg sock from child
* kill child?
* return sock
*/
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#ifdef HAVE_SETNS /* linux network namespaces */
#include <sched.h> /* setns / unshare */
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/wait.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clixon */
#include "clixon_err.h"
#include "clixon_log.h"
#include "clixon_netns.h"
#ifdef HAVE_SETNS
/*
* @thanks Anders Franzén
*/
static int
send_sock(int usock,
int fd)
{
int retval = -1;
int *fdptr;
struct msghdr msg={0};
struct cmsghdr *cmsg;
char buf[CMSG_SPACE(sizeof(fd))];
memset(buf,0,sizeof(buf));
msg.msg_control=buf;
msg.msg_controllen=sizeof(buf);
cmsg=CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level=SOL_SOCKET;
cmsg->cmsg_type=SCM_RIGHTS;
cmsg->cmsg_len=CMSG_LEN(sizeof(fd));
fdptr=(int *)CMSG_DATA(cmsg);
memcpy(fdptr,&fd,sizeof(fd));
msg.msg_controllen=CMSG_SPACE(sizeof(fd));
if (sendmsg(usock, &msg, 0) < 0){
clicon_err(OE_UNIX, errno, "sendmsg");
goto done;
}
retval = 0;
done:
return retval;
}
/*
* @thanks Anders Franzén
*/
static int
get_sock(int usock,
int *fd)
{
int retval = -1;
struct msghdr msg={0};
struct cmsghdr *cmsg;
char buf[128];
msg.msg_iov=0;
msg.msg_iovlen=0;
msg.msg_control=buf;
msg.msg_controllen=sizeof(buf);
/* Block here */
if (recvmsg(usock, &msg, 0) < 0){
clicon_err(OE_UNIX, errno, "recvmsg");
goto done;
}
cmsg=CMSG_FIRSTHDR(&msg);
memcpy(fd, CMSG_DATA(cmsg), sizeof(*fd));
retval = 0;
done:
return retval;
}
#endif /* HAVE_SETNS */
/*! Create and bind stream socket
* @param[in] sa Socketaddress
* @param[in] sa_len Length of sa. Tecynicaliyu to be independent of sockaddr sa_len
* @param[in] backlog Listen backlog, queie of pending connections
* @param[in] flags Socket flags Or:ed in with the socket(2) type parameter
* @param[in] addrstr Address string for debug
* @param[out] sock Server socket (bound for accept)
*/
static int
create_socket(struct sockaddr *sa,
size_t sin_len,
int backlog,
int flags,
const char *addrstr,
int *sock)
{
int retval = -1;
int s = -1;
int on = 1;
clicon_debug(1, "%s", __FUNCTION__);
if (sock == NULL){
clicon_err(OE_PROTO, EINVAL, "Requires socket output parameter");
goto done;
}
/* create inet socket */
#ifndef __APPLE__
flags = SOCK_STREAM | SOCK_CLOEXEC | flags;
#else
flags = SOCK_STREAM | flags;
#endif
if ((s = socket(sa->sa_family, flags,
0)) < 0) {
clicon_err(OE_UNIX, errno, "socket");
goto done;
}
#ifdef __APPLE__
if (fcntl(s, O_CLOEXEC)) {
clicon_err(OE_UNIX, errno, "fcntl");
goto done;
}
#endif
if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on)) == -1) {
clicon_err(OE_UNIX, errno, "setsockopt SO_KEEPALIVE");
goto done;
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on)) == -1) {
clicon_err(OE_UNIX, errno, "setsockopt SO_REUSEADDR");
goto done;
}
/* only bind ipv6, otherwise it may bind to ipv4 as well which is strange but seems default */
if (sa->sa_family == AF_INET6 &&
setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) == -1) {
clicon_err(OE_UNIX, errno, "setsockopt IPPROTO_IPV6");
goto done;
}
if (bind(s, sa, sin_len) == -1) {
/* Note may be ignored in upper layer by checking for EADDRNOTAVAIL, see eg restconf_openssl_init */
clicon_err(OE_UNIX, errno, "bind(%s)", addrstr);
goto done;
}
if (listen(s, backlog ) < 0){
clicon_err(OE_UNIX, errno, "listen");
goto done;
}
if (sock)
*sock = s;
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
if (retval != 0 && s != -1)
close(s);
return retval;
}
#ifdef HAVE_SETNS
/*! Fork a child, create and bind a socket in a separate network namespace and send back to parent
*
* @param[in] netns Network namespace
* @param[in] sa Socketaddress
* @param[in] sa_len Length of sa. Tecynicaliyu to be independent of sockaddr sa_len
* @param[in] backlog Listen backlog, queue of pending connections
* @param[in] flags Socket flags OR:ed in with the socket(2) type parameter
* @param[in] addrstr Address string for debug
* @param[out] sock Server socket (bound for accept)
*/
static int
fork_netns_socket(const char *netns,
struct sockaddr *sa,
size_t sin_len,
int backlog,
int flags,
const char *addrstr,
int *sock)
{
int retval = -1;
int sp[2] = {-1, -1};
pid_t child;
int wstatus = 0;
char nspath[MAXPATHLEN]; /* Path to namespace file */
struct stat st;
#ifdef __APPLE__
int sock_flags = SOCK_DGRAM;
#else
int sock_flags = SOCK_DGRAM | SOCK_CLOEXEC;
#endif
clicon_debug(1, "%s %s", __FUNCTION__, netns);
if (socketpair(AF_UNIX, sock_flags, 0, sp) < 0){
clicon_err(OE_UNIX, errno, "socketpair");
goto done;
}
#ifdef __APPLE__
if (fcntl(sp[0], O_CLOEXEC)) {
clicon_err(OE_UNIX, errno, "fcntl, sp[0]");
goto done;
}
if (fcntl(sp[1], O_CLOEXEC)) {
clicon_err(OE_UNIX, errno, "fcntl, sp[1]");
goto done;
}
#endif
/* Check namespace exists */
sprintf(nspath,"/var/run/netns/%s", netns);
if (stat(nspath, &st) < 0){
clicon_err(OE_UNIX, errno, ": stat(%s)", nspath);
goto done;
}
if ((child = fork()) < 0) {
clicon_err(OE_UNIX, errno, "fork");
goto done;
}
if (child == 0) { /* Child */
int fd;
int s = -1;
close(sp[0]);
/* Switch to namespace */
if ((fd=open(nspath, O_RDONLY)) < 0) {
clicon_err(OE_UNIX, errno, "open(%s)", nspath);
send_sock(sp[1], sp[1]); /* Dummy to wake parent */
exit(1); /* Dont do return here, need to exit child */
}
#ifdef HAVE_SETNS
if (setns(fd, CLONE_NEWNET) < 0){
clicon_err(OE_UNIX, errno, "setns(%s)", netns);
send_sock(sp[1], sp[1]); /* Dummy to wake parent */
exit(1); /* Dont do return here, need to exit child */
}
#endif
close(fd);
/* Create socket in this namespace */
if (create_socket(sa, sin_len, backlog, flags, addrstr, &s) < 0){
send_sock(sp[1], sp[1]); /* Dummy to wake parent */
exit(1); /* Dont do return here, need to exit child */
}
/* Send socket to parent */
if (send_sock(sp[1], s) < 0)
exit(1); /* Dont do return here, need to exit child */
close(s);
close(sp[1]);
exit(0);
}
/* Parent */
close(sp[1]);
if (get_sock(sp[0], sock) < 0)
goto done;
close(sp[0]);
if(waitpid(child, &wstatus, 0) == child)
; // retval = WEXITSTATUS(status); /* Dont know what to do with status */
if (WEXITSTATUS(wstatus)){
clicon_debug(1, "%s wstatus:%d", __FUNCTION__, WEXITSTATUS(wstatus));
*sock = -1;
clicon_err(OE_UNIX, EADDRNOTAVAIL, "bind(%s)", addrstr);
goto done;
}
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
return retval;
}
#endif /* HAVE_SETNS */
/*! Create and bind stream socket in network namespace
* @param[in] netns Network namespace
* @param[in] sa Socketaddress
* @param[in] sa_len Length of sa. Tecynicaliyu to be independent of sockaddr sa_len
* @param[in] backlog Listen backlog, queie of pending connections
* @param[in] flags Socket flags OR:ed in with the socket(2) type parameter
* @param[in] addrstr Address string for debug
* @param[out] sock Server socket (bound for accept)
*/
int
clixon_netns_socket(const char *netns,
struct sockaddr *sa,
size_t sin_len,
int backlog,
int flags,
const char *addrstr,
int *sock)
{
int retval = -1;
clicon_debug(1, "%s", __FUNCTION__);
if (netns == NULL){
if (create_socket(sa, sin_len, backlog, flags, addrstr, sock) < 0)
goto done;
goto ok;
}
else {
#ifdef HAVE_SETNS
if (fork_netns_socket(netns, sa, sin_len, backlog, flags, addrstr, sock) < 0)
goto done;
#else
clicon_err(OE_UNIX, errno, "No namespace support on platform: %s", netns);
return -1;
#endif
}
ok:
retval = 0;
done:
clicon_debug(1, "%s %d", __FUNCTION__, retval);
return retval;
}