Change in osmo-uecups[master]: add "start_program" support

This is merely a historical archive of years 2008-2021, before the migration to mailman3.

A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.

laforge gerrit-no-reply at lists.osmocom.org
Sat Apr 18 19:35:44 UTC 2020


laforge has submitted this change. ( https://gerrit.osmocom.org/c/osmo-uecups/+/17856 )

Change subject: add "start_program" support
......................................................................

add "start_program" support

This allows the controlling instance (ttcn3 test case) to start
a process (shell command) within the namespace of a given tunnel
/ tun device.

The controlling instance is informed of the success/failure
of starting the process, as well as the exit code at time of
termination.

Change-Id: I94db625de9f5968e53bf67ce2f941673d9a15fbc
Depends: libosmocore.git If1431f930f72a8d6c1d102426874a11b7a2debd9
Depends: libosmocore.git If8d89dd1f6989e1cd9b9367fad954d65f91ada30
---
M daemon/internal.h
M daemon/main.c
M daemon/tun_device.c
M ttcn3/UECUPS_Types.ttcn
4 files changed, 233 insertions(+), 1 deletion(-)

Approvals:
  laforge: Looks good to me, approved
  Jenkins Builder: Verified



diff --git a/daemon/internal.h b/daemon/internal.h
index f0e1382..09ba52e 100644
--- a/daemon/internal.h
+++ b/daemon/internal.h
@@ -116,6 +116,9 @@
 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);
 
 void _tun_device_deref_destroy(struct tun_device *tun);
@@ -202,11 +205,14 @@
 
 #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 @@
 	/* 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 <stdio.h>
 #include <assert.h>
 #include <sys/types.h>
+#include <sys/signalfd.h>
 #include <signal.h>
 #include <errno.h>
 
@@ -20,6 +21,7 @@
 #include <osmocom/core/stats.h>
 #include <osmocom/core/rate_ctr.h>
 #include <osmocom/core/socket.h>
+#include <osmocom/core/exec.h>
 #include <osmocom/vty/telnet_interface.h>
 #include <osmocom/vty/logging.h>
 #include <osmocom/vty/stats.h>
@@ -40,6 +42,8 @@
  * Client (Contol/User Plane Separation) Socket
  ***********************************************************************/
 
+#include <pwd.h>
+
 #define CUPS_MSGB_SIZE	1024
 
 #define LOGCC(cc, lvl, fmt, args ...)	\
@@ -55,6 +59,15 @@
 	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 @@
 	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 @@
 		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 @@
 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 @@
 	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 @@
 	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 @@
 	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 @@
 	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 @@
 	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
 };
 
 

-- 
To view, visit https://gerrit.osmocom.org/c/osmo-uecups/+/17856
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: osmo-uecups
Gerrit-Branch: master
Gerrit-Change-Id: I94db625de9f5968e53bf67ce2f941673d9a15fbc
Gerrit-Change-Number: 17856
Gerrit-PatchSet: 3
Gerrit-Owner: laforge <laforge at osmocom.org>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: laforge <laforge at osmocom.org>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20200418/5f27ca3d/attachment.htm>


More information about the gerrit-log mailing list