Asterisk - The Open Source Telephony Project GIT-master-4522eb1
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Modules Pages
res_audiosocket.c
Go to the documentation of this file.
1/*
2 * Asterisk -- An open source telephony toolkit.
3 *
4 * Copyright (C) 2019, CyCore Systems, Inc
5 *
6 * Seán C McCord <scm@cycoresys.com
7 *
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
13 *
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
17 */
18
19/*! \file
20 *
21 * \brief AudioSocket support for Asterisk
22 *
23 * \author Seán C McCord <scm@cycoresys.com>
24 *
25 */
26
27/*** MODULEINFO
28 <support_level>extended</support_level>
29 ***/
30
31#include "asterisk.h"
32#include "errno.h"
33#include <uuid/uuid.h>
34#include <arpa/inet.h> /* For byte-order conversion. */
35
36#include "asterisk/file.h"
38#include "asterisk/channel.h"
39#include "asterisk/module.h"
40#include "asterisk/uuid.h"
42
43#define MODULE_DESCRIPTION "AudioSocket support functions for Asterisk"
44
45#define MAX_CONNECT_TIMEOUT_MSEC 2000
46
47/*!
48 * \internal
49 * \brief Attempt to complete the audiosocket connection.
50 *
51 * \param server Url that we are trying to connect to.
52 * \param addr Address that host was resolved to.
53 * \param netsockfd File descriptor of socket.
54 *
55 * \retval 0 when connection is succesful.
56 * \retval 1 when there is an error.
57 */
58static int handle_audiosocket_connection(const char *server,
59 const struct ast_sockaddr addr, const int netsockfd)
60{
61 struct pollfd pfds[1];
62 int res, conresult;
63 socklen_t reslen;
64
65 reslen = sizeof(conresult);
66
67 pfds[0].fd = netsockfd;
68 pfds[0].events = POLLOUT;
69
70 while ((res = ast_poll(pfds, 1, MAX_CONNECT_TIMEOUT_MSEC)) != 1) {
71 if (errno != EINTR) {
72 if (!res) {
73 ast_log(LOG_WARNING, "AudioSocket connection to '%s' timed"
74 "out after MAX_CONNECT_TIMEOUT_MSEC (%d) milliseconds.\n",
76 } else {
77 ast_log(LOG_WARNING, "Connect to '%s' failed: %s\n", server,
78 strerror(errno));
79 }
80
81 return -1;
82 }
83 }
84
85 if (getsockopt(pfds[0].fd, SOL_SOCKET, SO_ERROR, &conresult, &reslen) < 0) {
86 ast_log(LOG_WARNING, "Connection to '%s' failed with error: %s\n",
87 ast_sockaddr_stringify(&addr), strerror(errno));
88 return -1;
89 }
90
91 if (conresult) {
92 ast_log(LOG_WARNING, "Connecting to '%s' failed for url '%s': %s\n",
93 ast_sockaddr_stringify(&addr), server, strerror(conresult));
94 return -1;
95 }
96
97 return 0;
98}
99
100const int ast_audiosocket_connect(const char *server, struct ast_channel *chan)
101{
102 int s = -1;
103 struct ast_sockaddr *addrs = NULL;
104 int num_addrs = 0, i = 0;
105
106 if (chan && ast_autoservice_start(chan) < 0) {
107 ast_log(LOG_WARNING, "Failed to start autoservice for channel "
108 "'%s'\n", ast_channel_name(chan));
109 goto end;
110 }
111
112 if (ast_strlen_zero(server)) {
113 ast_log(LOG_ERROR, "No AudioSocket server provided\n");
114 goto end;
115 }
116
117 if (!(num_addrs = ast_sockaddr_resolve(&addrs, server, PARSE_PORT_REQUIRE,
118 AST_AF_UNSPEC))) {
119 ast_log(LOG_ERROR, "Failed to resolve AudioSocket service using '%s' - "
120 "requires a valid hostname and port\n", server);
121 goto end;
122 }
123
124 /* Connect to AudioSocket service */
125 for (i = 0; i < num_addrs; i++) {
126
127 if (!ast_sockaddr_port(&addrs[i])) {
128 /* If there's no port, other addresses should have the
129 * same problem. Stop here.
130 */
131 ast_log(LOG_ERROR, "No port provided for '%s'\n",
132 ast_sockaddr_stringify(&addrs[i]));
133 s = -1;
134 goto end;
135 }
136
137 if ((s = ast_socket_nonblock(addrs[i].ss.ss_family, SOCK_STREAM,
138 IPPROTO_TCP)) < 0) {
139 ast_log(LOG_WARNING, "Unable to create socket: '%s'\n", strerror(errno));
140 continue;
141 }
142
143 /*
144 * Disable Nagle's algorithm by setting the TCP_NODELAY socket option.
145 * This reduces latency by preventing delays caused by packet buffering.
146 */
147 if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &(int){1}, sizeof(int)) < 0) {
148 ast_log(LOG_ERROR, "Failed to set TCP_NODELAY on AudioSocket: %s\n", strerror(errno));
149 }
150
151 if (ast_connect(s, &addrs[i]) && errno == EINPROGRESS) {
152
153 if (handle_audiosocket_connection(server, addrs[i], s)) {
154 close(s);
155 continue;
156 }
157
158 } else {
159 ast_log(LOG_ERROR, "Connection to '%s' failed with unexpected error: %s\n",
160 ast_sockaddr_stringify(&addrs[i]), strerror(errno));
161 close(s);
162 s = -1;
163 }
164
165 break;
166 }
167
168end:
169 if (addrs) {
170 ast_free(addrs);
171 }
172
173 if (chan && ast_autoservice_stop(chan) < 0) {
174 ast_log(LOG_WARNING, "Failed to stop autoservice for channel '%s'\n",
175 ast_channel_name(chan));
176 close(s);
177 return -1;
178 }
179
180 if (i == num_addrs) {
181 ast_log(LOG_ERROR, "Failed to connect to AudioSocket service\n");
182 close(s);
183 return -1;
184 }
185
186 return s;
187}
188
189const int ast_audiosocket_init(const int svc, const char *id)
190{
191 uuid_t uu;
192 int ret = 0;
193 uint8_t buf[3 + 16];
194
195 if (ast_strlen_zero(id)) {
196 ast_log(LOG_ERROR, "No UUID for AudioSocket\n");
197 return -1;
198 }
199
200 if (uuid_parse(id, uu)) {
201 ast_log(LOG_ERROR, "Failed to parse UUID '%s'\n", id);
202 return -1;
203 }
204
206 buf[1] = 0x00;
207 buf[2] = 0x10;
208 memcpy(buf + 3, uu, 16);
209
210 if (write(svc, buf, 3 + 16) != 3 + 16) {
211 ast_log(LOG_WARNING, "Failed to write data to AudioSocket because: %s\n", strerror(errno));
212 ret = -1;
213 }
214
215 return ret;
216}
217
218const int ast_audiosocket_send_frame(const int svc, const struct ast_frame *f)
219{
220 int datalen = f->datalen;
221 if (f->frametype == AST_FRAME_DTMF) {
222 datalen = 1;
223 }
224
225 {
226 uint8_t buf[3 + datalen];
227 uint16_t *length = (uint16_t *) &buf[1];
228
229 /* Audio format is 16-bit, 8kHz signed linear mono for dialplan app,
230 depends on agreed upon audio codec for channel driver interface. */
231 switch (f->frametype) {
232 case AST_FRAME_VOICE:
234 *length = htons(datalen);
235 memcpy(&buf[3], f->data.ptr, datalen);
236 break;
237 case AST_FRAME_DTMF:
239 buf[3] = (uint8_t) f->subclass.integer;
240 *length = htons(1);
241 break;
242 default:
243 ast_log(LOG_ERROR, "Unsupported frame type %d for AudioSocket\n", f->frametype);
244 return -1;
245 }
246
247 if (write(svc, buf, 3 + datalen) != 3 + datalen) {
248 ast_log(LOG_WARNING, "Failed to write data to AudioSocket because: %s\n", strerror(errno));
249 return -1;
250 }
251 }
252
253 return 0;
254}
255
257{
259}
260
262 int *const hangup)
263{
264 int i = 0, n = 0, ret = 0;
265 struct ast_frame f = {
267 .subclass.format = ast_format_slin,
268 .src = "AudioSocket",
269 .mallocd = AST_MALLOCD_DATA,
270 };
271 uint8_t header[3];
272 uint8_t *kind = &header[0];
273 uint16_t *length = (uint16_t *) &header[1];
274 uint8_t *data;
275
276 if (hangup) {
277 *hangup = 0;
278 }
279
280 n = read(svc, &header, 3);
281 if (n == -1) {
282 ast_log(LOG_WARNING, "Failed to read header from AudioSocket because: %s\n", strerror(errno));
283 return NULL;
284 }
285
286 if (n == 0 || *kind == AST_AUDIOSOCKET_KIND_HANGUP) {
287 /* Socket closure or requested hangup. */
288 if (hangup) {
289 *hangup = 1;
290 }
291 return NULL;
292 }
293
294 if (*kind != AST_AUDIOSOCKET_KIND_AUDIO) {
295 ast_log(LOG_ERROR, "Received AudioSocket message other than hangup or audio, refer to protocol specification for valid message types\n");
296 return NULL;
297 }
298
299 /* Swap endianess of length if needed. */
300 *length = ntohs(*length);
301 if (*length < 1) {
302 ast_log(LOG_ERROR, "Invalid message length received from AudioSocket server. \n");
303 return NULL;
304 }
305
306 data = ast_malloc(*length);
307 if (!data) {
308 ast_log(LOG_ERROR, "Failed to allocate for data from AudioSocket\n");
309 return NULL;
310 }
311
312 ret = 0;
313 n = 0;
314 i = 0;
315 while (i < *length) {
316 n = read(svc, data + i, *length - i);
317 if (n == -1) {
318 if (errno == EAGAIN || errno == EWOULDBLOCK) {
319 int poll_result = ast_wait_for_input(svc, 5);
320
321 if (poll_result == 1) {
322 continue;
323 } else if (poll_result == 0) {
324 ast_log(LOG_WARNING, "Poll timed out while waiting for data\n");
325 } else {
326 ast_log(LOG_WARNING, "Poll error: %s\n", strerror(errno));
327 }
328 }
329
330 ast_log(LOG_ERROR, "Failed to read payload from AudioSocket: %s\n", strerror(errno));
331 ret = -1;
332 break;
333 }
334 if (n == 0) {
335 ast_log(LOG_ERROR, "Insufficient payload read from AudioSocket\n");
336 ret = -1;
337 break;
338 }
339 i += n;
340 }
341
342 if (ret != 0) {
343 ast_free(data);
344 return NULL;
345 }
346
347 f.data.ptr = data;
348 f.datalen = *length;
349 f.samples = *length / 2;
350
351 /* The frame steals data, so it doesn't need to be freed here */
352 return ast_frisolate(&f);
353}
354
355static int load_module(void)
356{
357 ast_verb(5, "Loading AudioSocket Support module\n");
359}
360
361static int unload_module(void)
362{
363 ast_verb(5, "Unloading AudioSocket Support module\n");
365}
366
368 .support_level = AST_MODULE_SUPPORT_EXTENDED,
369 .load = load_module,
370 .unload = unload_module,
371 .load_pri = AST_MODPRI_CHANNEL_DEPEND,
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition: astmm.h:180
#define ast_malloc(len)
A wrapper for malloc()
Definition: astmm.h:191
#define ast_log
Definition: astobj2.c:42
static int hangup(void *data)
Definition: chan_pjsip.c:2526
General Asterisk PBX channel definitions.
const char * ast_channel_name(const struct ast_channel *chan)
int ast_autoservice_stop(struct ast_channel *chan)
Stop servicing a channel for us...
Definition: autoservice.c:266
int ast_autoservice_start(struct ast_channel *chan)
Automatically service a channel for us...
Definition: autoservice.c:200
char * end
Definition: eagi_proxy.c:73
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
Generic File Format Support. Should be included by clients of the file handling routines....
Media Format Cache API.
struct ast_format * ast_format_slin
Built-in cached signed linear 8kHz format.
Definition: format_cache.c:41
@ PARSE_PORT_REQUIRE
#define AST_FRAME_DTMF
#define ast_frisolate(fr)
Makes a frame independent of any static storage.
#define AST_MALLOCD_DATA
@ AST_FRAME_VOICE
#define LOG_ERROR
#define ast_verb(level,...)
#define LOG_WARNING
int errno
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition: module.h:331
@ AST_MODFLAG_GLOBAL_SYMBOLS
Definition: module.h:330
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:557
@ AST_MODPRI_CHANNEL_DEPEND
Definition: module.h:340
@ AST_MODULE_SUPPORT_EXTENDED
Definition: module.h:122
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
@ AST_MODULE_LOAD_SUCCESS
Definition: module.h:70
int ast_connect(int sockfd, const struct ast_sockaddr *addr)
Wrapper around connect(2) that uses struct ast_sockaddr.
Definition: netsock2.c:595
static char * ast_sockaddr_stringify(const struct ast_sockaddr *addr)
Wrapper around ast_sockaddr_stringify_fmt() with default format.
Definition: netsock2.h:256
#define ast_sockaddr_port(addr)
Get the port number of a socket address.
Definition: netsock2.h:517
int ast_sockaddr_resolve(struct ast_sockaddr **addrs, const char *str, int flags, int family)
Parses a string with an IPv4 or IPv6 address and place results into an array.
Definition: netsock2.c:280
@ AST_AF_UNSPEC
Definition: netsock2.h:54
#define ast_poll(a, b, c)
Definition: poll-compat.h:88
const int ast_audiosocket_init(const int svc, const char *id)
Send the initial message to an AudioSocket server.
const int ast_audiosocket_send_frame(const int svc, const struct ast_frame *f)
Send an Asterisk audio frame to an AudioSocket server.
struct ast_frame * ast_audiosocket_receive_frame(const int svc)
Receive an Asterisk frame from an AudioSocket server.
struct ast_frame * ast_audiosocket_receive_frame_with_hangup(const int svc, int *const hangup)
Receive an Asterisk frame from an AudioSocket server.
const int ast_audiosocket_connect(const char *server, struct ast_channel *chan)
Send the initial message to an AudioSocket server.
#define MAX_CONNECT_TIMEOUT_MSEC
static int load_module(void)
static int unload_module(void)
static int handle_audiosocket_connection(const char *server, const struct ast_sockaddr addr, const int netsockfd)
AudioSocket support functions.
@ AST_AUDIOSOCKET_KIND_AUDIO
Messages contains audio data, direction: Sent and received.
@ AST_AUDIOSOCKET_KIND_HANGUP
Message indicates the channel should be hung up, direction: Sent only.
@ AST_AUDIOSOCKET_KIND_UUID
Message contains the connection's UUID, direction: Received only.
@ AST_AUDIOSOCKET_KIND_DTMF
Message contains a DTMF digit, direction: Received only.
#define NULL
Definition: resample.c:96
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
Main Channel structure associated with a channel.
Data structure associated with a single frame of data.
union ast_frame::@228 data
struct ast_frame_subclass subclass
enum ast_frame_type frametype
Socket address structure.
Definition: netsock2.h:97
struct sockaddr_storage ss
Definition: netsock2.h:98
#define ast_socket_nonblock(domain, type, protocol)
Create a non-blocking socket.
Definition: utils.h:1079
int ast_wait_for_input(int fd, int ms)
Definition: utils.c:1698
Universally unique identifier support.