Core: Add support for systemd socket activation.

This change adds support for socket activation of certain SOCK_STREAM
listeners in Asterisk:
* AMI / AMI over TLS
* CLI
* HTTP / HTTPS

Example systemd units are provided.  This support extends to any socket
which is initialized using ast_tcptls_server_start, so any unknown
modules using this function will support socket activation.

Asterisk continues to function as normal if socket activation is not
enabled or if systemd development headers are not available during
build.

ASTERISK-27063 #close

Change-Id: Id814ee6a892f4b80d018365c8ad8d89063474f4d
This commit is contained in:
Corey Farrell 2017-06-18 20:24:04 -04:00
parent 58d50025a0
commit 70d2ccb9da
13 changed files with 384 additions and 3 deletions

119
contrib/systemd/README.txt Normal file
View File

@ -0,0 +1,119 @@
SystemD Socket Activation for Asterisk
======================================
This folder contains sample unit files which can be used as the basis of a
socket activated Asterisk deployment. Socket activation support currently
extends to the following listeners:
* Asterisk Command-line Interface
* Asterisk Manager Interface (clear text and TLS)
* Builtin HTTP / HTTPS server
The primary use case of this feature is to allow Asterisk to be started by
other services through use of AMI, CLI or REST API.
Security
========
Care must be take if enabling socket activation on any IP:PORT that is not
protected by a firewall. Any user that can reach any socket activation
port can start Asterisk, even if they do not have valid credentials to sign
into the service in question. Enabling HTTP socket activation on a system
which provides SIP over websockets would allow remote users to start Asterisk
any time the HTTP socket is running.
This functionality bypasses the normal restriction where only 'root' can start
a service. Enabling AMI socket activation allows any user on the local server
to start Asterisk by running 'telnet localhost 5038'.
CLI activation is secured by the combination of SocketUser, SocketGroup and
SocketMode settings in the systemd socket. Only local users with access will
be able to start asterisk by using CLI.
Separate .socket units or a single unit
=======================================
Asterisk is a complex system with many components which can be enabled or
disabled individually. Using socket activation requires deciding to use
a single socket file or multiple separate socket files.
The remainder of this README assumes separate socket units are used for each
listener.
Service and Socket files
========================
All .socket and .service examples in this folder use "reasonable" default
paths for Linux. Depending on your distribution and ./configure options
you may need to modify these before installing. The files are meant to
be examples rather than files to be blindly installed.
Installing and enabling socket units
====================================
Modify socket files as desired. Install them to a location where systemd
will find them. pkg-config can be used to determine an appropriate location.
For socket files to be managed directly by the local administrator:
pkg-config systemd --variable systemdsystemconfdir
For socket files to be deployed by package manager:
pkg-config systemd --variable systemdsystemunitdir
After installing socket files you must run 'systemctl daemon-reload' for
systemd to read the added/modified units. After this you can enable the
desired sockets, for example to enable AMI:
systemctl enable asterisk-ami.socket
Socket Selection
================
Asterisk configuration is unchanged by use of socket activation. When a
component that supports socket activation starts a listener in Asterisk,
any sockets provided by systemd are iterated. The systemd socket is used
when the bound address configured by Asterisk is an exact match with the
address given by the ListenStream setting in the systemd socket.
Command-line Interface
======================
Symbolic links do not appear to be resolved when checking the CLI listener.
This may be of concern since /var/run is often a symbolic link to /run. Both
Asterisk and systemd must use /var/run, or both must use /run. Mismatching
will result in service startup failure.
When socket activation is used for Asterisk CLI some asterisk.conf options
are ignored. The following options from the [files] section are ignored
and must instead be set by the systemd socket file.
* astctlowner - use SocketUser
* astctlgroup - use SocketGroup
* astctlpermissions - use SocketMode
See asterisk-cli.socket for an example of these settings.
Stopping Asterisk
=================
Some existing asterisk.service files use CLI 'core stop now' for the ExecStop
command. It is not recommended to use CLI to stop Asterisk on systems where
CLI socket activation is enabled. If Asterisk fails to start systemd still
tries running the ExecStop command. This can result in an loop where ExecStop
causes CLI socket activation to start Asterisk again. A better way to deal
with shutdown is to use Type=notify and do not specify an ExecStop command.
See the example asterisk.service.
Unused Sockets
==============
Asterisk makes no attempt to check for sockets provided by systemd that are not
used. It is the users responsibility to only provide sockets which Asterisk is
configured to use.

View File

@ -0,0 +1,10 @@
[Unit]
Description=Asterisk Manager Interface Socket
[Socket]
Service=asterisk.service
ListenStream=0.0.0.0:5038
[Install]
WantedBy=sockets.target
RequiredBy=asterisk.service

View File

@ -0,0 +1,10 @@
[Unit]
Description=Asterisk Manager Interface TLS Socket
[Socket]
Service=asterisk.service
ListenStream=0.0.0.0:5039
[Install]
WantedBy=sockets.target
RequiredBy=asterisk.service

View File

@ -0,0 +1,13 @@
[Unit]
Description=Asterisk Command-line Interface Socket
[Socket]
Service=asterisk.service
ListenStream=/var/run/asterisk/asterisk.ctl
SocketUser=asterisk
SocketGroup=asterisk
SocketMode=0660
[Install]
WantedBy=sockets.target
RequiredBy=asterisk.service

View File

@ -0,0 +1,11 @@
[Unit]
Description=Asterisk HTTP Socket
[Socket]
Service=asterisk.service
FreeBind=true
ListenStream=127.0.0.1:8088
[Install]
WantedBy=sockets.target
RequiredBy=asterisk.service

View File

@ -0,0 +1,11 @@
[Unit]
Description=Asterisk HTTPS Socket
[Socket]
Service=asterisk.service
FreeBind=true
ListenStream=127.0.0.1:8089
[Install]
WantedBy=sockets.target
RequiredBy=asterisk.service

View File

@ -0,0 +1,27 @@
[Unit]
Description=Asterisk PBX and telephony daemon.
After=network.target
[Service]
Type=notify
Environment=HOME=/var/lib/asterisk
WorkingDirectory=/var/lib/asterisk
User=asterisk
Group=asterisk
ExecStart=/usr/sbin/asterisk -mqf -C /etc/asterisk/asterisk.conf
ExecReload=/usr/sbin/asterisk -rx 'core reload'
#Nice=0
#UMask=0002
LimitCORE=infinity
#LimitNOFILE=
Restart=always
RestartSec=4
# Prevent duplication of logs with color codes to /var/log/messages
StandardOutput=null
PrivateTmp=true
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,26 @@
[Unit]
Description=Asterisk Sockets
[Socket]
FreeBind=true
SocketUser=asterisk
SocketGroup=asterisk
SocketMode=0660
# CLI
ListenStream=/var/run/asterisk/asterisk.ctl
# AMI
ListenStream=0.0.0.0:5038
# AMIS
ListenStream=0.0.0.0:5039
# HTTP
ListenStream=127.0.0.1:8088
# HTTPS
ListenStream=127.0.0.1:8089
# chan_sip TCP
ListenStream=0.0.0.0:5060
# chan_sip TLS
ListenStream=0.0.0.0:5061
[Install]
WantedBy=sockets.target

View File

@ -24,6 +24,7 @@
#define _ASTERISK_IO_H
#include "asterisk/poll-compat.h"
#include "asterisk/netsock2.h"
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
@ -148,6 +149,29 @@ int ast_get_termcols(int fd);
*/
int ast_sd_notify(const char *state);
/*!
* \brief Find a listening file descriptor provided by socket activation.
* \param type SOCK_STREAM or SOCK_DGRAM
* \param addr The socket address of the bound listener.
* \retval <0 No match.
* \retval >0 File Descriptor matching sockaddr.
*
* \note This function returns -1 if systemd's development headers were not
* detected on the system.
*/
int ast_sd_get_fd(int type, const struct ast_sockaddr *addr);
/*!
* \brief Find a listening AF_LOCAL file descriptor provided by socket activation.
* \param type SOCK_STREAM or SOCK_DGRAM
* \param path The path of the listener.
* \retval <0 No match.
* \retval >0 File Descriptor matching path.
*
* \note This function returns -1 if systemd's development headers were not
* detected on the system.
*/
int ast_sd_get_fd_un(int type, const char *path);
#if defined(__cplusplus) || defined(c_plusplus)
}

View File

@ -128,6 +128,22 @@ static inline void ast_sockaddr_setnull(struct ast_sockaddr *addr)
addr->len = 0;
}
/*!
* \brief
* Copies the data from a sockaddr to an ast_sockaddr
*
* \param dst The destination ast_sockaddr
* \param src The source sockaddr
* \param len Length of the value stored in sockaddr
* \retval void
*/
static inline void ast_sockaddr_copy_sockaddr(struct ast_sockaddr *dst,
struct sockaddr *src, socklen_t len)
{
memcpy(dst, src, len);
dst->len = len;
}
/*!
* \since 1.8
*

View File

@ -350,6 +350,7 @@ struct ast_eid ast_eid_default;
char record_cache_dir[AST_CACHE_DIR_LEN] = DEFAULT_TMP_DIR;
static int ast_socket = -1; /*!< UNIX Socket for allowing remote control */
static int ast_socket_is_sd = 0; /*!< Is socket activation responsible for ast_socket? */
static int ast_consock = -1; /*!< UNIX Socket for controlling another asterisk */
pid_t ast_mainpid;
struct console {
@ -1576,8 +1577,16 @@ static int ast_makesocket(void)
uid_t uid = -1;
gid_t gid = -1;
for (x = 0; x < AST_MAX_CONNECTS; x++)
for (x = 0; x < AST_MAX_CONNECTS; x++) {
consoles[x].fd = -1;
}
if (ast_socket_is_sd) {
ast_socket = ast_sd_get_fd_un(SOCK_STREAM, ast_config_AST_SOCKET);
goto start_lthread;
}
unlink(ast_config_AST_SOCKET);
ast_socket = socket(PF_LOCAL, SOCK_STREAM, 0);
if (ast_socket < 0) {
@ -1602,12 +1611,19 @@ static int ast_makesocket(void)
return -1;
}
start_lthread:
if (ast_pthread_create_background(&lthread, NULL, listener, NULL)) {
ast_log(LOG_WARNING, "Unable to create listener thread.\n");
close(ast_socket);
return -1;
}
if (ast_socket_is_sd) {
/* owner/group/permissions are set by systemd, we might not even have access
* to socket file so leave it alone */
return 0;
}
if (!ast_strlen_zero(ast_config_AST_CTL_OWNER)) {
struct passwd *pw;
if ((pw = getpwnam(ast_config_AST_CTL_OWNER)) == NULL)
@ -2075,7 +2091,9 @@ static void really_quit(int num, shutdown_nice_t niceness, int restart)
pthread_cancel(lthread);
close(ast_socket);
ast_socket = -1;
unlink(ast_config_AST_SOCKET);
if (!ast_socket_is_sd) {
unlink(ast_config_AST_SOCKET);
}
pthread_kill(lthread, SIGURG);
pthread_join(lthread, NULL);
}
@ -4317,7 +4335,12 @@ int main(int argc, char *argv[])
/* Initial value of the maximum active system verbosity level. */
ast_verb_sys_level = option_verbose;
if (ast_tryconnect()) {
if (ast_sd_get_fd_un(SOCK_STREAM, ast_config_AST_SOCKET) > 0) {
ast_socket_is_sd = 1;
}
/* DO NOT perform check for existing daemon if systemd has CLI socket activation */
if (!ast_socket_is_sd && ast_tryconnect()) {
/* One is already running */
if (ast_opt_remote) {
multi_thread_safe = 1;

View File

@ -36,6 +36,10 @@
#include "asterisk/utils.h"
#ifdef HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#ifndef SD_LISTEN_FDS_START
#define SD_LISTEN_FDS_START 3
#endif
#endif
#ifdef DEBUG_IO
@ -392,3 +396,73 @@ int ast_sd_notify(const char *state) {
return 0;
#endif
}
/*!
* \internal \brief Check the type and sockaddr of a file descriptor.
* \param fd File Descriptor to check.
* \param type SOCK_STREAM or SOCK_DGRAM
* \param addr The socket address to match.
* \retval 0 if matching
* \retval -1 if not matching
*/
#ifdef HAVE_SYSTEMD
static int ast_sd_is_socket_sockaddr(int fd, int type, const struct ast_sockaddr* addr)
{
int canretry = 1;
struct ast_sockaddr fd_addr;
struct sockaddr ss;
socklen_t ss_len;
if (sd_is_socket(fd, AF_UNSPEC, type, 1) <= 0) {
return -1;
}
doretry:
if (getsockname(fd, &ss, &ss_len) != 0) {
return -1;
}
if (ss.sa_family == AF_UNSPEC && canretry) {
/* An unknown bug can cause silent failure from
* the first call to getsockname. */
canretry = 0;
goto doretry;
}
ast_sockaddr_copy_sockaddr(&fd_addr, &ss, ss_len);
return ast_sockaddr_cmp(addr, &fd_addr);
}
#endif
int ast_sd_get_fd(int type, const struct ast_sockaddr *addr)
{
#ifdef HAVE_SYSTEMD
int count = sd_listen_fds(0);
int idx;
for (idx = 0; idx < count; idx++) {
if (!ast_sd_is_socket_sockaddr(idx + SD_LISTEN_FDS_START, type, addr)) {
return idx + SD_LISTEN_FDS_START;
}
}
#endif
return -1;
}
int ast_sd_get_fd_un(int type, const char *path)
{
#ifdef HAVE_SYSTEMD
int count = sd_listen_fds(0);
int idx;
for (idx = 0; idx < count; idx++) {
if (sd_is_socket_unix(idx + SD_LISTEN_FDS_START, type, 1, path, 0) > 0) {
return idx + SD_LISTEN_FDS_START;
}
}
#endif
return -1;
}

View File

@ -40,6 +40,7 @@
#include "asterisk/compat.h"
#include "asterisk/tcptls.h"
#include "asterisk/io.h"
#include "asterisk/http.h"
#include "asterisk/utils.h"
#include "asterisk/strings.h"
@ -618,6 +619,7 @@ void ast_tcptls_server_start(struct ast_tcptls_session_args *desc)
int flags;
int x = 1;
int tls_changed = 0;
int sd_socket;
if (desc->tls_cfg) {
char hash[41];
@ -689,6 +691,19 @@ void ast_tcptls_server_start(struct ast_tcptls_session_args *desc)
pthread_join(desc->master, NULL);
}
sd_socket = ast_sd_get_fd(SOCK_STREAM, &desc->local_address);
if (sd_socket != -1) {
if (desc->accept_fd != sd_socket) {
if (desc->accept_fd != -1) {
close(desc->accept_fd);
}
desc->accept_fd = sd_socket;
}
goto systemd_socket_activation;
}
if (desc->accept_fd != -1) {
close(desc->accept_fd);
}
@ -718,6 +733,8 @@ void ast_tcptls_server_start(struct ast_tcptls_session_args *desc)
ast_log(LOG_ERROR, "Unable to listen for %s!\n", desc->name);
goto error;
}
systemd_socket_activation:
flags = fcntl(desc->accept_fd, F_GETFL);
fcntl(desc->accept_fd, F_SETFL, flags | O_NONBLOCK);
if (ast_pthread_create_background(&desc->master, NULL, desc->accept_fn, desc)) {