diff --git a/daemon/internal.h b/daemon/internal.h index f0e1382..09ba52e 100644 --- a/daemon/internal.h +++ b/daemon/internal.h @@ -115,6 +115,9 @@ struct tun_device { struct tun_device * tun_device_find_or_create(struct gtp_daemon *d, const char *devname, const char *netns_name); +struct tun_device * +tun_device_find_netns(struct gtp_daemon *d, const char *netns_name); + struct tun_device * _tun_device_find(struct gtp_daemon *d, const char *devname); @@ -202,11 +205,14 @@ bool gtp_tunnel_destroy(struct gtp_daemon *d, const struct sockaddr_storage *bin #define UECUPS_SCTP_PORT 4268 +struct osmo_signalfd; + struct gtp_daemon { /* global lists of various objects */ struct llist_head gtp_endpoints; struct llist_head tun_devices; struct llist_head gtp_tunnels; + struct llist_head subprocesses; /* lock protecting all of the above lists */ pthread_rwlock_t rwlock; /* main thread ID */ @@ -214,6 +220,7 @@ struct gtp_daemon { /* client CUPS interface */ struct llist_head cups_clients; struct osmo_stream_srv_link *cups_link; + struct osmo_signalfd *signalfd; struct { char *cups_local_ip; diff --git a/daemon/main.c b/daemon/main.c index c49b771..155d5d5 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +42,8 @@ * Client (Contol/User Plane Separation) Socket ***********************************************************************/ +#include + #define CUPS_MSGB_SIZE 1024 #define LOGCC(cc, lvl, fmt, args ...) \ @@ -55,6 +59,15 @@ struct cups_client { char sockname[OSMO_SOCK_NAME_MAXLEN]; }; +struct subprocess { + /* member in daemon->cups_clients */ + struct llist_head list; + /* pointer to the client that started us */ + struct cups_client *cups_client; + /* PID of the process */ + pid_t pid; +}; + /* Send JSON to a given client/connection */ static int cups_client_tx_json(struct cups_client *cc, json_t *jtx) { @@ -289,6 +302,151 @@ static int cups_client_handle_destroy_tun(struct cups_client *cc, json_t *dtun) return 0; } +static json_t *gen_uecups_term_ind(pid_t pid, int status) +{ + json_t *jterm = json_object(); + json_t *jret = json_object(); + + json_object_set_new(jterm, "pid", json_integer(pid)); + json_object_set_new(jterm, "exit_code", json_integer(status)); + + json_object_set_new(jret, "program_term_ind", jterm); + + return jret; +} + + +static struct subprocess *subprocess_by_pid(struct gtp_daemon *d, pid_t pid) +{ + struct subprocess *sproc; + llist_for_each_entry(sproc, &d->subprocesses, list) { + if (sproc->pid == pid) + return sproc; + } + return NULL; +} + +static void sigchild_cb(struct osmo_signalfd *osfd, const struct signalfd_siginfo *fdsi) +{ + struct gtp_daemon *d = osfd->data; + struct subprocess *sproc; + json_t *jterm_ind; + + OSMO_ASSERT(fdsi->ssi_signo == SIGCHLD); + + LOGP(DUECUPS, LOGL_DEBUG, "SIGCHLD receive from pid %u; status=%d\n", + fdsi->ssi_pid, fdsi->ssi_status); + + sproc = subprocess_by_pid(d, fdsi->ssi_pid); + if (!sproc) { + LOGP(DUECUPS, LOGL_NOTICE, "subprocess %u terminated (status=%d) but we don't know it?\n", + fdsi->ssi_pid, fdsi->ssi_status); + return; + } + + /* FIXME: generate prog_term_ind towards control plane */ + jterm_ind = gen_uecups_term_ind(fdsi->ssi_pid, fdsi->ssi_status); + if (!jterm_ind) + return; + + cups_client_tx_json(sproc->cups_client, jterm_ind); + + llist_del(&sproc->list); + talloc_free(sproc); +} + +static json_t *gen_uecups_start_res(pid_t pid, const char *result) +{ + json_t *ret = gen_uecups_result("start_program_res", result); + json_object_set_new(json_object_get(ret, "start_program_res"), "pid", json_integer(pid)); + + return ret; +} + +static int cups_client_handle_start_program(struct cups_client *cc, json_t *sprog) +{ + json_t *juser, *jcmd, *jenv, *jnetns, *jres; + struct gtp_daemon *d = cc->d; + const char *cmd, *user; + char **addl_env = NULL; + sigset_t oldmask; + int nsfd, rc; + + juser = json_object_get(sprog, "run_as_user"); + jcmd = json_object_get(sprog, "command"); + jenv = json_object_get(sprog, "environment"); + jnetns = json_object_get(sprog, "tun_netns_name"); + + /* mandatory parts */ + if (!juser || !jcmd) + return -EINVAL; + if (!json_is_string(juser) || !json_is_string(jcmd)) + return -EINVAL; + + /* optional parts */ + if (jenv && !json_is_array(jenv)) + return -EINVAL; + if (jnetns && !json_is_string(jnetns)) + return -EINVAL; + + cmd = json_string_value(jcmd); + user = json_string_value(juser); + if (jnetns) { + struct tun_device *tun = tun_device_find_netns(d, json_string_value(jnetns)); + if (!tun) + return -ENODEV; + nsfd = tun->netns_fd; + } + + /* build environment */ + if (jenv) { + json_t *j; + int i; + addl_env = talloc_zero_array(cc, char *, json_array_size(jenv)+1); + if (!addl_env) + return -ENOMEM; + json_array_foreach(jenv, i, j) { + addl_env[i] = talloc_strdup(addl_env, json_string_value(j)); + } + } + + if (jnetns) { + rc = switch_ns(nsfd, &oldmask); + if (rc < 0) { + talloc_free(addl_env); + return -EIO; + } + } + + rc = osmo_system_nowait2(cmd, osmo_environment_whitelist, addl_env, user); + + if (jnetns) { + OSMO_ASSERT(restore_ns(&oldmask) == 0); + } + + talloc_free(addl_env); + + if (rc > 0) { + /* create a record about the subprocess we started, so we can notify the + * client that crated it upon termination */ + struct subprocess *sproc = talloc_zero(cc, struct subprocess); + if (!sproc) + return -ENOMEM; + + sproc->cups_client = cc; + sproc->pid = rc; + llist_add_tail(&sproc->list, &d->subprocesses); + jres = gen_uecups_start_res(sproc->pid, "OK"); + } else { + jres = gen_uecups_start_res(0, "ERR_INVALID_DATA"); + } + + cups_client_tx_json(cc, jres); + + return 0; +} + + static int cups_client_handle_json(struct cups_client *cc, json_t *jroot) { void *iter; @@ -309,6 +467,8 @@ static int cups_client_handle_json(struct cups_client *cc, json_t *jroot) rc = cups_client_handle_create_tun(cc, cmd); } else if (!strcmp(key, "destroy_tun")) { rc = cups_client_handle_destroy_tun(cc, cmd); + } else if (!strcmp(key, "start_program")) { + rc = cups_client_handle_start_program(cc, cmd); } else { LOGCC(cc, LOGL_NOTICE, "Unknown command '%s' received\n", key); return -EINVAL; @@ -387,6 +547,17 @@ out: static int cups_client_closed_cb(struct osmo_stream_srv *conn) { struct cups_client *cc = osmo_stream_srv_get_data(conn); + struct gtp_daemon *d = cc->d; + struct subprocess *p, *p2; + + /* kill + forget about all subprocesses of this client */ + llist_for_each_entry_safe(p, p2, &d->subprocesses, list) { + if (p->cups_client == cc) { + kill(p->pid, SIGKILL); + llist_del(&p->list); + talloc_free(p); + } + } LOGCC(cc, LOGL_INFO, "UECUPS connection lost\n"); llist_del(&cc->list); @@ -404,6 +575,7 @@ static int cups_accept_cb(struct osmo_stream_srv_link *link, int fd) if (!cc) return -1; + cc->d = d; osmo_sock_get_name_buf(cc->sockname, sizeof(cc->sockname), fd); cc->srv = osmo_stream_srv_create(cc, link, fd, cups_client_read_cb, cups_client_closed_cb, cc); if (!cc->srv) { @@ -439,6 +611,7 @@ static struct gtp_daemon *gtp_daemon_alloc(void *ctx) INIT_LLIST_HEAD(&d->gtp_endpoints); INIT_LLIST_HEAD(&d->tun_devices); INIT_LLIST_HEAD(&d->gtp_tunnels); + INIT_LLIST_HEAD(&d->subprocesses); pthread_rwlock_init(&d->rwlock, NULL); d->main_thread = pthread_self(); @@ -528,6 +701,13 @@ int main(int argc, char **argv) osmo_stream_srv_link_set_accept_cb(g_daemon->cups_link, cups_accept_cb); osmo_stream_srv_link_open(g_daemon->cups_link); + /* block SIGCHLD via normal delivery; redirect it to signalfd */ + sigset_t sigset; + sigemptyset(&sigset); + sigaddset(&sigset, SIGCHLD); + sigprocmask(SIG_BLOCK, &sigset, NULL); + g_daemon->signalfd = osmo_signalfd_setup(g_daemon, sigset, sigchild_cb, g_daemon); + if (g_daemonize) { rc = osmo_daemonize(); if (rc < 0) { diff --git a/daemon/tun_device.c b/daemon/tun_device.c index f6553ca..e20607d 100644 --- a/daemon/tun_device.c +++ b/daemon/tun_device.c @@ -337,6 +337,23 @@ _tun_device_find(struct gtp_daemon *d, const char *devname) return NULL; } +/* find the first tun device within given named netns */ +struct tun_device * +tun_device_find_netns(struct gtp_daemon *d, const char *netns_name) +{ + struct tun_device *tun; + + pthread_rwlock_rdlock(&d->rwlock); + llist_for_each_entry(tun, &d->tun_devices, list) { + if (!strcmp(tun->netns_name, netns_name)) { + pthread_rwlock_unlock(&d->rwlock); + return tun; + } + } + pthread_rwlock_unlock(&d->rwlock); + return NULL; +} + struct tun_device * tun_device_find_or_create(struct gtp_daemon *d, const char *devname, const char *netns_name) { diff --git a/ttcn3/UECUPS_Types.ttcn b/ttcn3/UECUPS_Types.ttcn index 3699dee..8861998 100644 --- a/ttcn3/UECUPS_Types.ttcn +++ b/ttcn3/UECUPS_Types.ttcn @@ -56,11 +56,39 @@ type record UECUPS_DestroyTunRes { UECUPS_Result result }; +/* User requests deaemon to start a program in given network namespace */ +type record UECUPS_StartProgram { + /* the command to be started (with optional environment entries) */ + charstring command, + charstring_list environment optional, + /* user + group to use when starting command */ + charstring run_as_user, + /* network namespace in which to start the command */ + charstring tun_netns_name optional +}; +type record of charstring charstring_list; + +/* Daemon informs us that a program has been started */ +type record UECUPS_StartProgramRes { + UECUPS_Result result, + integer pid +}; + +/* Daemon informs us that a program has terminated */ +type record UECUPS_ProgramTermInd { + integer pid, + integer exit_code +}; + + type union PDU_UECUPS { UECUPS_CreateTun create_tun, UECUPS_CreateTunRes create_tun_res, UECUPS_DestroyTun destroy_tun, - UECUPS_DestroyTunRes destroy_tun_res + UECUPS_DestroyTunRes destroy_tun_res, + UECUPS_StartProgram start_program, + UECUPS_StartProgramRes start_program_res, + UECUPS_ProgramTermInd program_term_ind };