<p>Jenkins2 <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/6052">View Change</a></p><div style="white-space:pre-wrap">Approvals:
Joshua Colp: Looks good to me, but someone else must approve
George Joseph: Looks good to me, approved
Jenkins2: Approved for Submit
</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">Core: Add support for systemd socket activation.<br><br>This change adds support for socket activation of certain SOCK_STREAM<br>listeners in Asterisk:<br>* AMI / AMI over TLS<br>* CLI<br>* HTTP / HTTPS<br><br>Example systemd units are provided. This support extends to any socket<br>which is initialized using ast_tcptls_server_start, so any unknown<br>modules using this function will support socket activation.<br><br>Asterisk continues to function as normal if socket activation is not<br>enabled or if systemd development headers are not available during<br>build.<br><br>ASTERISK-27063 #close<br><br>Change-Id: Id814ee6a892f4b80d018365c8ad8d89063474f4d<br>---<br>A contrib/systemd/README.txt<br>A contrib/systemd/asterisk-ami.socket<br>A contrib/systemd/asterisk-amis.socket<br>A contrib/systemd/asterisk-cli.socket<br>A contrib/systemd/asterisk-http.socket<br>A contrib/systemd/asterisk-https.socket<br>A contrib/systemd/asterisk.service<br>A contrib/systemd/asterisk.socket<br>M include/asterisk/io.h<br>M include/asterisk/netsock2.h<br>M main/asterisk.c<br>M main/io.c<br>M main/tcptls.c<br>13 files changed, 384 insertions(+), 3 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/contrib/systemd/README.txt b/contrib/systemd/README.txt<br>new file mode 100644<br>index 0000000..3225641<br>--- /dev/null<br>+++ b/contrib/systemd/README.txt<br>@@ -0,0 +1,119 @@<br>+SystemD Socket Activation for Asterisk<br>+======================================<br>+<br>+This folder contains sample unit files which can be used as the basis of a<br>+socket activated Asterisk deployment. Socket activation support currently<br>+extends to the following listeners:<br>+<br>+* Asterisk Command-line Interface<br>+* Asterisk Manager Interface (clear text and TLS)<br>+* Builtin HTTP / HTTPS server<br>+<br>+The primary use case of this feature is to allow Asterisk to be started by<br>+other services through use of AMI, CLI or REST API.<br>+<br>+<br>+Security<br>+========<br>+<br>+Care must be take if enabling socket activation on any IP:PORT that is not<br>+protected by a firewall. Any user that can reach any socket activation<br>+port can start Asterisk, even if they do not have valid credentials to sign<br>+into the service in question. Enabling HTTP socket activation on a system<br>+which provides SIP over websockets would allow remote users to start Asterisk<br>+any time the HTTP socket is running.<br>+<br>+This functionality bypasses the normal restriction where only 'root' can start<br>+a service. Enabling AMI socket activation allows any user on the local server<br>+to start Asterisk by running 'telnet localhost 5038'.<br>+<br>+CLI activation is secured by the combination of SocketUser, SocketGroup and<br>+SocketMode settings in the systemd socket. Only local users with access will<br>+be able to start asterisk by using CLI.<br>+<br>+<br>+Separate .socket units or a single unit<br>+=======================================<br>+<br>+Asterisk is a complex system with many components which can be enabled or<br>+disabled individually. Using socket activation requires deciding to use<br>+a single socket file or multiple separate socket files.<br>+<br>+The remainder of this README assumes separate socket units are used for each<br>+listener.<br>+<br>+<br>+Service and Socket files<br>+========================<br>+<br>+All .socket and .service examples in this folder use "reasonable" default<br>+paths for Linux. Depending on your distribution and ./configure options<br>+you may need to modify these before installing. The files are meant to<br>+be examples rather than files to be blindly installed.<br>+<br>+<br>+Installing and enabling socket units<br>+====================================<br>+<br>+Modify socket files as desired. Install them to a location where systemd<br>+will find them. pkg-config can be used to determine an appropriate location.<br>+<br>+For socket files to be managed directly by the local administrator:<br>+ pkg-config systemd --variable systemdsystemconfdir<br>+<br>+For socket files to be deployed by package manager:<br>+ pkg-config systemd --variable systemdsystemunitdir<br>+<br>+<br>+After installing socket files you must run 'systemctl daemon-reload' for<br>+systemd to read the added/modified units. After this you can enable the<br>+desired sockets, for example to enable AMI:<br>+ systemctl enable asterisk-ami.socket<br>+<br>+<br>+Socket Selection<br>+================<br>+<br>+Asterisk configuration is unchanged by use of socket activation. When a<br>+component that supports socket activation starts a listener in Asterisk,<br>+any sockets provided by systemd are iterated. The systemd socket is used<br>+when the bound address configured by Asterisk is an exact match with the<br>+address given by the ListenStream setting in the systemd socket.<br>+<br>+<br>+Command-line Interface<br>+======================<br>+<br>+Symbolic links do not appear to be resolved when checking the CLI listener.<br>+This may be of concern since /var/run is often a symbolic link to /run. Both<br>+Asterisk and systemd must use /var/run, or both must use /run. Mismatching<br>+will result in service startup failure.<br>+<br>+When socket activation is used for Asterisk CLI some asterisk.conf options<br>+are ignored. The following options from the [files] section are ignored<br>+and must instead be set by the systemd socket file.<br>+* astctlowner - use SocketUser<br>+* astctlgroup - use SocketGroup<br>+* astctlpermissions - use SocketMode<br>+<br>+See asterisk-cli.socket for an example of these settings.<br>+<br>+<br>+Stopping Asterisk<br>+=================<br>+<br>+Some existing asterisk.service files use CLI 'core stop now' for the ExecStop<br>+command. It is not recommended to use CLI to stop Asterisk on systems where<br>+CLI socket activation is enabled. If Asterisk fails to start systemd still<br>+tries running the ExecStop command. This can result in an loop where ExecStop<br>+causes CLI socket activation to start Asterisk again. A better way to deal<br>+with shutdown is to use Type=notify and do not specify an ExecStop command.<br>+See the example asterisk.service.<br>+<br>+<br>+Unused Sockets<br>+==============<br>+<br>+Asterisk makes no attempt to check for sockets provided by systemd that are not<br>+used. It is the users responsibility to only provide sockets which Asterisk is<br>+configured to use.<br>diff --git a/contrib/systemd/asterisk-ami.socket b/contrib/systemd/asterisk-ami.socket<br>new file mode 100644<br>index 0000000..1fd45e4<br>--- /dev/null<br>+++ b/contrib/systemd/asterisk-ami.socket<br>@@ -0,0 +1,10 @@<br>+[Unit]<br>+Description=Asterisk Manager Interface Socket<br>+<br>+[Socket]<br>+Service=asterisk.service<br>+ListenStream=0.0.0.0:5038<br>+<br>+[Install]<br>+WantedBy=sockets.target<br>+RequiredBy=asterisk.service<br>diff --git a/contrib/systemd/asterisk-amis.socket b/contrib/systemd/asterisk-amis.socket<br>new file mode 100644<br>index 0000000..c17cee3<br>--- /dev/null<br>+++ b/contrib/systemd/asterisk-amis.socket<br>@@ -0,0 +1,10 @@<br>+[Unit]<br>+Description=Asterisk Manager Interface TLS Socket<br>+<br>+[Socket]<br>+Service=asterisk.service<br>+ListenStream=0.0.0.0:5039<br>+<br>+[Install]<br>+WantedBy=sockets.target<br>+RequiredBy=asterisk.service<br>diff --git a/contrib/systemd/asterisk-cli.socket b/contrib/systemd/asterisk-cli.socket<br>new file mode 100644<br>index 0000000..9161a7b<br>--- /dev/null<br>+++ b/contrib/systemd/asterisk-cli.socket<br>@@ -0,0 +1,13 @@<br>+[Unit]<br>+Description=Asterisk Command-line Interface Socket<br>+<br>+[Socket]<br>+Service=asterisk.service<br>+ListenStream=/var/run/asterisk/asterisk.ctl<br>+SocketUser=asterisk<br>+SocketGroup=asterisk<br>+SocketMode=0660<br>+<br>+[Install]<br>+WantedBy=sockets.target<br>+RequiredBy=asterisk.service<br>diff --git a/contrib/systemd/asterisk-http.socket b/contrib/systemd/asterisk-http.socket<br>new file mode 100644<br>index 0000000..e6862b5<br>--- /dev/null<br>+++ b/contrib/systemd/asterisk-http.socket<br>@@ -0,0 +1,11 @@<br>+[Unit]<br>+Description=Asterisk HTTP Socket<br>+<br>+[Socket]<br>+Service=asterisk.service<br>+FreeBind=true<br>+ListenStream=127.0.0.1:8088<br>+<br>+[Install]<br>+WantedBy=sockets.target<br>+RequiredBy=asterisk.service<br>diff --git a/contrib/systemd/asterisk-https.socket b/contrib/systemd/asterisk-https.socket<br>new file mode 100644<br>index 0000000..d9240dd<br>--- /dev/null<br>+++ b/contrib/systemd/asterisk-https.socket<br>@@ -0,0 +1,11 @@<br>+[Unit]<br>+Description=Asterisk HTTPS Socket<br>+<br>+[Socket]<br>+Service=asterisk.service<br>+FreeBind=true<br>+ListenStream=127.0.0.1:8089<br>+<br>+[Install]<br>+WantedBy=sockets.target<br>+RequiredBy=asterisk.service<br>diff --git a/contrib/systemd/asterisk.service b/contrib/systemd/asterisk.service<br>new file mode 100644<br>index 0000000..c3d4648<br>--- /dev/null<br>+++ b/contrib/systemd/asterisk.service<br>@@ -0,0 +1,27 @@<br>+[Unit]<br>+Description=Asterisk PBX and telephony daemon.<br>+After=network.target<br>+<br>+[Service]<br>+Type=notify<br>+Environment=HOME=/var/lib/asterisk<br>+WorkingDirectory=/var/lib/asterisk<br>+User=asterisk<br>+Group=asterisk<br>+ExecStart=/usr/sbin/asterisk -mqf -C /etc/asterisk/asterisk.conf<br>+ExecReload=/usr/sbin/asterisk -rx 'core reload'<br>+<br>+#Nice=0<br>+#UMask=0002<br>+LimitCORE=infinity<br>+#LimitNOFILE=<br>+Restart=always<br>+RestartSec=4<br>+<br>+# Prevent duplication of logs with color codes to /var/log/messages<br>+StandardOutput=null<br>+<br>+PrivateTmp=true<br>+<br>+[Install]<br>+WantedBy=multi-user.target<br>diff --git a/contrib/systemd/asterisk.socket b/contrib/systemd/asterisk.socket<br>new file mode 100644<br>index 0000000..afdca0d<br>--- /dev/null<br>+++ b/contrib/systemd/asterisk.socket<br>@@ -0,0 +1,26 @@<br>+[Unit]<br>+Description=Asterisk Sockets<br>+<br>+[Socket]<br>+FreeBind=true<br>+SocketUser=asterisk<br>+SocketGroup=asterisk<br>+SocketMode=0660<br>+<br>+# CLI<br>+ListenStream=/var/run/asterisk/asterisk.ctl<br>+# AMI<br>+ListenStream=0.0.0.0:5038<br>+# AMIS<br>+ListenStream=0.0.0.0:5039<br>+# HTTP<br>+ListenStream=127.0.0.1:8088<br>+# HTTPS<br>+ListenStream=127.0.0.1:8089<br>+# chan_sip TCP<br>+ListenStream=0.0.0.0:5060<br>+# chan_sip TLS<br>+ListenStream=0.0.0.0:5061<br>+<br>+[Install]<br>+WantedBy=sockets.target<br>diff --git a/include/asterisk/io.h b/include/asterisk/io.h<br>index 6ee8450..f103cf5 100644<br>--- a/include/asterisk/io.h<br>+++ b/include/asterisk/io.h<br>@@ -24,6 +24,7 @@<br> #define _ASTERISK_IO_H<br> <br> #include "asterisk/poll-compat.h"<br>+#include "asterisk/netsock2.h"<br> <br> #if defined(__cplusplus) || defined(c_plusplus)<br> extern "C" {<br>@@ -148,6 +149,29 @@<br> */<br> int ast_sd_notify(const char *state);<br> <br>+/*!<br>+ * \brief Find a listening file descriptor provided by socket activation.<br>+ * \param type SOCK_STREAM or SOCK_DGRAM<br>+ * \param addr The socket address of the bound listener.<br>+ * \retval <0 No match.<br>+ * \retval >0 File Descriptor matching sockaddr.<br>+ *<br>+ * \note This function returns -1 if systemd's development headers were not<br>+ * detected on the system.<br>+ */<br>+int ast_sd_get_fd(int type, const struct ast_sockaddr *addr);<br>+<br>+/*!<br>+ * \brief Find a listening AF_LOCAL file descriptor provided by socket activation.<br>+ * \param type SOCK_STREAM or SOCK_DGRAM<br>+ * \param path The path of the listener.<br>+ * \retval <0 No match.<br>+ * \retval >0 File Descriptor matching path.<br>+ *<br>+ * \note This function returns -1 if systemd's development headers were not<br>+ * detected on the system.<br>+ */<br>+int ast_sd_get_fd_un(int type, const char *path);<br> <br> #if defined(__cplusplus) || defined(c_plusplus)<br> }<br>diff --git a/include/asterisk/netsock2.h b/include/asterisk/netsock2.h<br>index 3ede990..6c0dd63 100644<br>--- a/include/asterisk/netsock2.h<br>+++ b/include/asterisk/netsock2.h<br>@@ -129,6 +129,22 @@<br> }<br> <br> /*!<br>+ * \brief<br>+ * Copies the data from a sockaddr to an ast_sockaddr<br>+ *<br>+ * \param dst The destination ast_sockaddr<br>+ * \param src The source sockaddr<br>+ * \param len Length of the value stored in sockaddr<br>+ * \retval void<br>+ */<br>+static inline void ast_sockaddr_copy_sockaddr(struct ast_sockaddr *dst,<br>+ struct sockaddr *src, socklen_t len)<br>+{<br>+ memcpy(dst, src, len);<br>+ dst->len = len;<br>+}<br>+<br>+/*!<br> * \since 1.8<br> *<br> * \brief<br>diff --git a/main/asterisk.c b/main/asterisk.c<br>index 0429838..a302836 100644<br>--- a/main/asterisk.c<br>+++ b/main/asterisk.c<br>@@ -350,6 +350,7 @@<br> char record_cache_dir[AST_CACHE_DIR_LEN] = DEFAULT_TMP_DIR;<br> <br> static int ast_socket = -1; /*!< UNIX Socket for allowing remote control */<br>+static int ast_socket_is_sd = 0; /*!< Is socket activation responsible for ast_socket? */<br> static int ast_consock = -1; /*!< UNIX Socket for controlling another asterisk */<br> pid_t ast_mainpid;<br> struct console {<br>@@ -1576,8 +1577,16 @@<br> uid_t uid = -1;<br> gid_t gid = -1;<br> <br>- for (x = 0; x < AST_MAX_CONNECTS; x++)<br>+ for (x = 0; x < AST_MAX_CONNECTS; x++) {<br> consoles[x].fd = -1;<br>+ }<br>+<br>+ if (ast_socket_is_sd) {<br>+ ast_socket = ast_sd_get_fd_un(SOCK_STREAM, ast_config_AST_SOCKET);<br>+<br>+ goto start_lthread;<br>+ }<br>+<br> unlink(ast_config_AST_SOCKET);<br> ast_socket = socket(PF_LOCAL, SOCK_STREAM, 0);<br> if (ast_socket < 0) {<br>@@ -1602,10 +1611,17 @@<br> return -1;<br> }<br> <br>+start_lthread:<br> if (ast_pthread_create_background(<hread, NULL, listener, NULL)) {<br> ast_log(LOG_WARNING, "Unable to create listener thread.\n");<br> close(ast_socket);<br> return -1;<br>+ }<br>+<br>+ if (ast_socket_is_sd) {<br>+ /* owner/group/permissions are set by systemd, we might not even have access<br>+ * to socket file so leave it alone */<br>+ return 0;<br> }<br> <br> if (!ast_strlen_zero(ast_config_AST_CTL_OWNER)) {<br>@@ -2075,7 +2091,9 @@<br> pthread_cancel(lthread);<br> close(ast_socket);<br> ast_socket = -1;<br>- unlink(ast_config_AST_SOCKET);<br>+ if (!ast_socket_is_sd) {<br>+ unlink(ast_config_AST_SOCKET);<br>+ }<br> pthread_kill(lthread, SIGURG);<br> pthread_join(lthread, NULL);<br> }<br>@@ -4319,7 +4337,12 @@<br> /* Initial value of the maximum active system verbosity level. */<br> ast_verb_sys_level = option_verbose;<br> <br>- if (ast_tryconnect()) {<br>+ if (ast_sd_get_fd_un(SOCK_STREAM, ast_config_AST_SOCKET) > 0) {<br>+ ast_socket_is_sd = 1;<br>+ }<br>+<br>+ /* DO NOT perform check for existing daemon if systemd has CLI socket activation */<br>+ if (!ast_socket_is_sd && ast_tryconnect()) {<br> /* One is already running */<br> if (ast_opt_remote) {<br> multi_thread_safe = 1;<br>diff --git a/main/io.c b/main/io.c<br>index b063c22..ed455df 100644<br>--- a/main/io.c<br>+++ b/main/io.c<br>@@ -36,6 +36,10 @@<br> #include "asterisk/utils.h"<br> #ifdef HAVE_SYSTEMD<br> #include <systemd/sd-daemon.h><br>+<br>+#ifndef SD_LISTEN_FDS_START<br>+#define SD_LISTEN_FDS_START 3<br>+#endif<br> #endif<br> <br> #ifdef DEBUG_IO<br>@@ -392,3 +396,73 @@<br> return 0;<br> #endif<br> }<br>+<br>+/*!<br>+ * \internal \brief Check the type and sockaddr of a file descriptor.<br>+ * \param fd File Descriptor to check.<br>+ * \param type SOCK_STREAM or SOCK_DGRAM<br>+ * \param addr The socket address to match.<br>+ * \retval 0 if matching<br>+ * \retval -1 if not matching<br>+ */<br>+#ifdef HAVE_SYSTEMD<br>+static int ast_sd_is_socket_sockaddr(int fd, int type, const struct ast_sockaddr* addr)<br>+{<br>+ int canretry = 1;<br>+ struct ast_sockaddr fd_addr;<br>+ struct sockaddr ss;<br>+ socklen_t ss_len;<br>+<br>+ if (sd_is_socket(fd, AF_UNSPEC, type, 1) <= 0) {<br>+ return -1;<br>+ }<br>+<br>+doretry:<br>+ if (getsockname(fd, &ss, &ss_len) != 0) {<br>+ return -1;<br>+ }<br>+<br>+ if (ss.sa_family == AF_UNSPEC && canretry) {<br>+ /* An unknown bug can cause silent failure from<br>+ * the first call to getsockname. */<br>+ canretry = 0;<br>+ goto doretry;<br>+ }<br>+<br>+ ast_sockaddr_copy_sockaddr(&fd_addr, &ss, ss_len);<br>+<br>+ return ast_sockaddr_cmp(addr, &fd_addr);<br>+}<br>+#endif<br>+<br>+int ast_sd_get_fd(int type, const struct ast_sockaddr *addr)<br>+{<br>+#ifdef HAVE_SYSTEMD<br>+ int count = sd_listen_fds(0);<br>+ int idx;<br>+<br>+ for (idx = 0; idx < count; idx++) {<br>+ if (!ast_sd_is_socket_sockaddr(idx + SD_LISTEN_FDS_START, type, addr)) {<br>+ return idx + SD_LISTEN_FDS_START;<br>+ }<br>+ }<br>+#endif<br>+<br>+ return -1;<br>+}<br>+<br>+int ast_sd_get_fd_un(int type, const char *path)<br>+{<br>+#ifdef HAVE_SYSTEMD<br>+ int count = sd_listen_fds(0);<br>+ int idx;<br>+<br>+ for (idx = 0; idx < count; idx++) {<br>+ if (sd_is_socket_unix(idx + SD_LISTEN_FDS_START, type, 1, path, 0) > 0) {<br>+ return idx + SD_LISTEN_FDS_START;<br>+ }<br>+ }<br>+#endif<br>+<br>+ return -1;<br>+}<br>diff --git a/main/tcptls.c b/main/tcptls.c<br>index a3c7dfa..85859a3 100644<br>--- a/main/tcptls.c<br>+++ b/main/tcptls.c<br>@@ -40,6 +40,7 @@<br> <br> #include "asterisk/compat.h"<br> #include "asterisk/tcptls.h"<br>+#include "asterisk/io.h"<br> #include "asterisk/http.h"<br> #include "asterisk/utils.h"<br> #include "asterisk/strings.h"<br>@@ -618,6 +619,7 @@<br> int flags;<br> int x = 1;<br> int tls_changed = 0;<br>+ int sd_socket;<br> <br> if (desc->tls_cfg) {<br> char hash[41];<br>@@ -689,6 +691,19 @@<br> pthread_join(desc->master, NULL);<br> }<br> <br>+ sd_socket = ast_sd_get_fd(SOCK_STREAM, &desc->local_address);<br>+<br>+ if (sd_socket != -1) {<br>+ if (desc->accept_fd != sd_socket) {<br>+ if (desc->accept_fd != -1) {<br>+ close(desc->accept_fd);<br>+ }<br>+ desc->accept_fd = sd_socket;<br>+ }<br>+<br>+ goto systemd_socket_activation;<br>+ }<br>+<br> if (desc->accept_fd != -1) {<br> close(desc->accept_fd);<br> }<br>@@ -718,6 +733,8 @@<br> ast_log(LOG_ERROR, "Unable to listen for %s!\n", desc->name);<br> goto error;<br> }<br>+<br>+systemd_socket_activation:<br> flags = fcntl(desc->accept_fd, F_GETFL);<br> fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK);<br> if (ast_pthread_create_background(&desc->master, NULL, desc->accept_fn, desc)) {<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/6052">change 6052</a>. To unsubscribe, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/6052"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: 15 </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: Id814ee6a892f4b80d018365c8ad8d89063474f4d </div>
<div style="display:none"> Gerrit-Change-Number: 6052 </div>
<div style="display:none"> Gerrit-PatchSet: 1 </div>
<div style="display:none"> Gerrit-Owner: Corey Farrell <git@cfware.com> </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins2 </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>