/*
  * 
  * 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;
}
