From fdbdd1fd575d7d37d4fb182252107a4d3816853c Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Mon, 12 Jan 2015 01:07:19 +0100 Subject: [PATCH] Abstract sockets for server and audiod. Currently para_server and para_audiod won't start if the socket specials can not be created, for example because /var/paraslash does not exist or has insufficient permissions. The abstract namespace feature for local sockets allows to go without socket specials, as implemented in this commit. The feature is a non-portable Linux extension though, so a fallback to pathname sockets is necessary for other operating systems and for backward compatibility. For para_server the situation is simple because the socket is created by the afs process, and only command handlers are supposed to connect. Since afs and the command handlers are part of the same executable (para_server), there are no compatibility issues. Hence we simply use an abstract socket on Linux and a pathname socket elsewhere. For para_audiod things are more complicated because two executables are involved: para_audioc and para_audiod. To allow older versions of para_audioc to connect to recent versions of para_audiod, we let audiod listen on one socket of either type. Startup fails only if neither socket can be created. As for the implementation, we make use of the fact that it makes not much sense to call create_local_socket() with permission mode equal to 0, and that there are no permission modes for abstract sockets. With the patch applied, specifying the mode as zero instructs create_local_socket() to create an abstract socket. In this case we also use the given pathname but prepend a zero byte at the front of the ->sun_path member of struct sockaddr_un to bind(2) the socket to an address in the abstract namespace. On the client side (connect_local_socket()) we first try to connect to an abstract address and fall back to pathnames sockets on errors. This change is transparent to the callers of connect_local_socket. Hence para_audioc and afs command handlers need no modifications at all. The patch also adds a section on abstract sockets to the user manual. --- afs.c | 12 ++++++--- audiod.c | 71 +++++++++++++++++++++++++++++++-------------------- net.c | 43 ++++++++++++++++++++----------- web/manual.m4 | 29 +++++++++++++++++++++ 4 files changed, 108 insertions(+), 47 deletions(-) diff --git a/afs.c b/afs.c index c677b6cd..65d6ed95 100644 --- a/afs.c +++ b/afs.c @@ -639,11 +639,15 @@ static int setup_command_socket_or_die(void) char *socket_name = conf.afs_socket_arg; unlink(socket_name); - ret = create_local_socket(socket_name, - S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH); + ret = create_local_socket(socket_name, 0); if (ret < 0) { - PARA_EMERG_LOG("%s: %s\n", para_strerror(-ret), socket_name); - exit(EXIT_FAILURE); + ret = create_local_socket(socket_name, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH); + if (ret < 0) { + PARA_EMERG_LOG("%s: %s\n", para_strerror(-ret), + socket_name); + exit(EXIT_FAILURE); + } } socket_fd = ret; PARA_INFO_LOG("listening on socket %s (fd %d)\n", socket_name, diff --git a/audiod.c b/audiod.c index e8a82b5e..acf41376 100644 --- a/audiod.c +++ b/audiod.c @@ -155,14 +155,22 @@ static struct status_task status_task_struct; */ static struct status_task *stat_task = &status_task_struct; -/** - * the task for handling audiod commands +/* + * The task for handling audiod commands. + * + * We need two listening sockets for backward compability: on Linux systems + * fd[0] is an abstract socket (more precisely, a socket bound to an address in + * the abstract namespace), and fd[1] is the usual pathname socket. On other + * systems, fd[0] is negative, and only the pathname socket is used. * - * \sa struct task, struct sched + * For 0.5.x we accept connections on both sockets to make sure that old + * para_audioc versions can still connect. New versions use only the abstract + * socket. Hence after v0.6.0 we can go back to a single socket, either an + * abstract one (Linux) or a pathname socket (all other systems). */ struct command_task { - /** the local listening socket */ - int fd; + /** The local listening sockets. */ + int fd[2]; /** the associated task structure */ struct task *task; }; @@ -956,10 +964,8 @@ static int parse_stream_args(void) } /* does not unlink socket on errors */ -static int audiod_get_socket(void) +static void init_local_sockets(struct command_task *ct) { - int ret; - if (conf.socket_given) socket_name = para_strdup(conf.socket_arg); else { @@ -971,13 +977,12 @@ static int audiod_get_socket(void) PARA_NOTICE_LOG("local socket: %s\n", socket_name); if (conf.force_given) unlink(socket_name); - ret = create_local_socket(socket_name, + ct->fd[0] = create_local_socket(socket_name, 0); + ct->fd[1] = create_local_socket(socket_name, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IWOTH); - if (ret < 0) - goto err; - return ret; -err: - PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + if (ct->fd[0] >= 0 || ct->fd[1] >= 0) + return; + PARA_EMERG_LOG("%s\n", para_strerror(-ct->fd[1])); exit(EXIT_FAILURE); } @@ -1010,28 +1015,38 @@ static int signal_post_select(struct sched *s, void *context) static void command_pre_select(struct sched *s, void *context) { struct command_task *ct = context; - para_fd_set(ct->fd, &s->rfds, &s->max_fileno); + int i; + + for (i = 0; i < 2; i++) + if (ct->fd[i] >= 0) + para_fd_set(ct->fd[i], &s->rfds, &s->max_fileno); } static int command_post_select(struct sched *s, void *context) { - int ret; + int ret, i; struct command_task *ct = context; static struct timeval last_status_dump; struct timeval tmp, delay; - bool force = true; + bool force = false; ret = task_get_notification(ct->task); if (ret < 0) return ret; - ret = handle_connect(ct->fd, &s->rfds); - if (ret < 0) { - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); - if (ret == -E_AUDIOD_TERM) { - task_notify_all(s, -ret); - return ret; - } - } else if (ret > 0) + for (i = 0; i < 2; i++) { + if (ct->fd[i] < 0) + continue; + ret = handle_connect(ct->fd[i], &s->rfds); + if (ret < 0) { + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + if (ret == -E_AUDIOD_TERM) { + task_notify_all(s, -ret); + return ret; + } + } else if (ret > 0) + force = true; + } + if (force == true) goto dump; /* if last status dump was less than 500ms ago, do nothing */ @@ -1048,8 +1063,8 @@ static int command_post_select(struct sched *s, void *context) delay.tv_sec = 5; delay.tv_usec = 0; tv_add(&last_status_dump, &delay, &tmp); - if (tv_diff(now, &tmp, NULL) < 0) - force = false; + if (tv_diff(now, &tmp, NULL) > 0) + force = true; dump: audiod_status_dump(force); last_status_dump = *now; @@ -1058,7 +1073,7 @@ dump: static void init_command_task(struct command_task *ct) { - ct->fd = audiod_get_socket(); /* doesn't return on errors */ + init_local_sockets(ct); /* doesn't return on errors */ ct->task = task_register(&(struct task_info) { .name = "command", diff --git a/net.c b/net.c index 9698e427..463033bb 100644 --- a/net.c +++ b/net.c @@ -834,13 +834,14 @@ int dccp_available_ccids(uint8_t **ccid_array) * \return Positive on success, \p -E_NAME_TOO_LONG if \a name is longer * than \p UNIX_PATH_MAX. */ -static int init_unix_addr(struct sockaddr_un *u, const char *name) +static int init_unix_addr(struct sockaddr_un *u, const char *name, + bool abstract) { - if (strlen(name) >= UNIX_PATH_MAX) + if (strlen(name) + abstract >= UNIX_PATH_MAX) return -E_NAME_TOO_LONG; memset(u->sun_path, 0, UNIX_PATH_MAX); u->sun_family = PF_UNIX; - strcpy(u->sun_path, name); + strcpy(u->sun_path + abstract, name); return 1; } @@ -855,6 +856,10 @@ static int init_unix_addr(struct sockaddr_un *u, const char *name) * nonblocking mode and listen(2) is called to prepare the socket for * accepting incoming connection requests. * + * If mode is zero, an abstract socket (a non-portable Linux extension) is + * created. In this case the socket name has no connection with filesystem + * pathnames. + * * \return The file descriptor on success, negative error code on failure. * * \sa socket(2), \sa bind(2), \sa chmod(2), listen(2), unix(7). @@ -863,8 +868,9 @@ int create_local_socket(const char *name, mode_t mode) { struct sockaddr_un unix_addr; int fd, ret; + bool abstract = mode == 0; - ret = init_unix_addr(&unix_addr, name); + ret = init_unix_addr(&unix_addr, name, abstract); if (ret < 0) return ret; ret = socket(PF_UNIX, SOCK_STREAM, 0); @@ -874,14 +880,16 @@ int create_local_socket(const char *name, mode_t mode) ret = mark_fd_nonblocking(fd); if (ret < 0) goto err; - ret = bind(fd, (struct sockaddr *)&unix_addr, UNIX_PATH_MAX); + ret = bind(fd, (struct sockaddr *)&unix_addr, sizeof(unix_addr)); if (ret < 0) { ret = -ERRNO_TO_PARA_ERROR(errno); goto err; } - ret = -E_CHMOD; - if (chmod(name, mode) < 0) - goto err; + if (!abstract) { + ret = -E_CHMOD; + if (chmod(name, mode) < 0) + goto err; + } if (listen(fd , 5) < 0) { ret = -ERRNO_TO_PARA_ERROR(errno); goto err; @@ -911,17 +919,22 @@ int connect_local_socket(const char *name) int fd, ret; PARA_DEBUG_LOG("connecting to %s\n", name); - ret = init_unix_addr(&unix_addr, name); - if (ret < 0) - return ret; fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) return -ERRNO_TO_PARA_ERROR(errno); - if (connect(fd, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) == -1) { - ret = -ERRNO_TO_PARA_ERROR(errno); + /* first try (linux-only) abstract socket */ + ret = init_unix_addr(&unix_addr, name, true); + if (ret < 0) goto err; - } - return fd; + if (connect(fd, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) != -1) + return fd; + /* next try pathname socket */ + ret = init_unix_addr(&unix_addr, name, false); + if (ret < 0) + goto err; + if (connect(fd, (struct sockaddr *)&unix_addr, sizeof(unix_addr)) != -1) + return fd; + ret = -ERRNO_TO_PARA_ERROR(errno); err: close(fd); return ret; diff --git a/web/manual.m4 b/web/manual.m4 index 0a5f580a..0182c515 100644 --- a/web/manual.m4 +++ b/web/manual.m4 @@ -2145,6 +2145,35 @@ maintain state for each listening receiver, multicast often implies connectionless transport, which is the reason that it is currently only available via UDP. +Abstract socket namespace +~~~~~~~~~~~~~~~~~~~~~~~~~ +UNIX domain sockets are a traditional way to communicate between +processes on the same machine. They are always reliable (see above) +and don't reorder datagrams. Unlike TCP and UDP, UNIX domain sockets +support passing open file descriptors or process credentials to +other processes. + +The usual way to set up a UNIX domain socket (as obtained from +socket(2)) for listening is to first bind the socket to a file system +pathname and then call listen(2), then accept(2). Such sockets are +called _pathname sockets_ because bind(2) creates a special socket +file at the specified path. Pathname sockets allow unrelated processes +to communicate with the listening process by binding to the same path +and calling connect(2). + +There are two problems with pathname sockets: + + * The listing process must be able to (safely) create the + socket special in a directory which is also accessible to + the connecting process. + + * After an unclean shutdown of the listening process, a stale + socket special may reside on the file system. + +The abstract socket namespace is a non-portable Linux feature which +avoids these problems. Abstract sockets are still bound to a name, +but the name has no connection with file system pathnames. + License ~~~~~~~ -- 2.39.2