1 /*
2  * Copyright (c) 2010-2014 Sippy Software, Inc., http://www.sippysoft.com
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  */
27 
28 #if defined(HAVE_CONFIG_H)
29 #include "config_pp.h"
30 #endif
31 
32 #include <sys/types.h>
33 #include <sys/socket.h>
34 #include <netinet/in.h>
35 #include <assert.h>
36 #include <errno.h>
37 #include <pthread.h>
38 #include <signal.h>
39 #include <stddef.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <unistd.h>
44 
45 #include "rtpp_log.h"
46 #include "rtpp_types.h"
47 #include "rtpp_refcnt.h"
48 #include "rtpp_log_obj.h"
49 #include "rtpp_network.h"
50 #include "rtpp_notify.h"
51 #include "rtpp_queue.h"
52 #include "rtpp_tnotify_tgt.h"
53 #include "rtpp_mallocs.h"
54 #include "rtpp_wi.h"
55 #include "rtpp_wi_private.h"
56 
57 struct rtpp_notify_wi
58 {
59     int len;
60     struct rtpp_tnotify_target *rttp;
61     char notify_buf[0];
62 };
63 
64 struct rtpp_notify_priv {
65     struct rtpp_notify pub;
66     struct rtpp_queue *nqueue;
67     struct rtpp_wi *sigterm;
68     pthread_t thread_id;
69     struct rtpp_log *glog;
70 };
71 
72 #define PUB2PVT(pubp)      ((struct rtpp_notify_priv *)((char *)(pubp) - offsetof(struct rtpp_notify_priv, pub)))
73 
74 static int rtpp_notify_schedule(struct rtpp_notify *,
75   struct rtpp_tnotify_target *, const char *);
76 static void rtpp_notify_dtor(struct rtpp_notify *);
77 static void do_timeout_notification(struct rtpp_notify_wi *, int, struct rtpp_log *);
78 
79 static void
rtpp_notify_queue_run(void * arg)80 rtpp_notify_queue_run(void *arg)
81 {
82     struct rtpp_wi *wi;
83     struct rtpp_notify_wi *wi_data;
84     struct rtpp_notify_priv *pvt;
85 
86     pvt = (struct rtpp_notify_priv *)arg;
87     for (;;) {
88         wi = rtpp_queue_get_item(pvt->nqueue, 0);
89         if (rtpp_wi_get_type(wi) == RTPP_WI_TYPE_SGNL) {
90             rtpp_wi_free(wi);
91             break;
92         }
93         wi_data = rtpp_wi_data_get_ptr(wi, sizeof(struct rtpp_notify_wi), 0);
94 
95         /* main work here */
96         do_timeout_notification(wi_data, 3, wi->log);
97 
98         /* deallocate wi */
99         rtpp_wi_free(wi);
100     }
101 }
102 
103 struct rtpp_notify *
rtpp_notify_ctor(struct rtpp_log * glog)104 rtpp_notify_ctor(struct rtpp_log *glog)
105 {
106     struct rtpp_notify_priv *pvt;
107 
108     pvt = rtpp_zmalloc(sizeof(struct rtpp_notify_priv));
109     if (pvt == NULL) {
110         goto e0;
111     }
112     pvt->nqueue = rtpp_queue_init(1, "rtpp_notify");
113     if (pvt->nqueue == NULL) {
114         goto e1;
115     }
116 
117     /* Pre-allocate sigterm, so that we don't have any malloc() in dtor() */
118     pvt->sigterm = rtpp_wi_malloc_sgnl(SIGTERM, NULL, 0);
119     if (pvt->sigterm == NULL) {
120         goto e2;
121     }
122 
123     if (pthread_create(&pvt->thread_id, NULL, (void *(*)(void *))&rtpp_notify_queue_run, pvt) != 0) {
124         goto e3;
125     }
126 
127     CALL_SMETHOD(glog->rcnt, incref);
128     pvt->glog = glog;
129     pvt->pub.schedule = &rtpp_notify_schedule;
130     pvt->pub.dtor = &rtpp_notify_dtor;
131 
132     return (&pvt->pub);
133 
134 e3:
135     rtpp_wi_free(pvt->sigterm);
136 e2:
137     rtpp_queue_destroy(pvt->nqueue);
138 e1:
139     free(pvt);
140 e0:
141     return (NULL);
142 }
143 
144 static void
rtpp_notify_dtor(struct rtpp_notify * pub)145 rtpp_notify_dtor(struct rtpp_notify *pub)
146 {
147     struct rtpp_notify_priv *pvt;
148 
149     pvt = PUB2PVT(pub);
150 
151     rtpp_queue_put_item(pvt->sigterm, pvt->nqueue);
152     pthread_join(pvt->thread_id, NULL);
153     rtpp_queue_destroy(pvt->nqueue);
154     CALL_SMETHOD(pvt->glog->rcnt, decref);
155     free(pvt);
156 }
157 
158 static int
rtpp_notify_schedule(struct rtpp_notify * pub,struct rtpp_tnotify_target * rttp,const char * notify_tag)159 rtpp_notify_schedule(struct rtpp_notify *pub,
160   struct rtpp_tnotify_target *rttp, const char *notify_tag)
161 {
162     struct rtpp_notify_wi *wi_data;
163     struct rtpp_wi *wi;
164     int len;
165     struct rtpp_notify_priv *pvt;
166 
167     pvt = PUB2PVT(pub);
168 
169     /* string, \0 and \n */
170     len = strlen(notify_tag) + 2;
171 
172     wi = rtpp_wi_malloc_udata((void **)&wi_data,
173       sizeof(struct rtpp_notify_wi) + len);
174     if (wi == NULL) {
175         return (-1);
176     }
177     memset(wi_data, '\0', sizeof(struct rtpp_notify_wi) + len);
178 
179     wi_data->rttp = rttp;
180     wi_data->len = len;
181 
182     len = snprintf(wi_data->notify_buf, len, "%s\n", notify_tag);
183 
184     CALL_SMETHOD(pvt->glog->rcnt, incref);
185     wi->log = pvt->glog;
186 
187     rtpp_queue_put_item(wi, pvt->nqueue);
188     return (0);
189 }
190 
191 static void
reconnect_timeout_handler(struct rtpp_log * log,struct rtpp_tnotify_target * rttp)192 reconnect_timeout_handler(struct rtpp_log *log, struct rtpp_tnotify_target *rttp)
193 {
194 
195     assert (rttp->connected == 0);
196 
197     if (rttp->fd == -1) {
198         RTPP_LOG(log, RTPP_LOG_DBUG, "connecting timeout socket");
199     } else {
200         RTPP_LOG(log, RTPP_LOG_DBUG, "reconnecting timeout socket");
201         close(rttp->fd);
202     }
203     rttp->fd = socket(rttp->socket_type, SOCK_STREAM, 0);
204     if (rttp->fd == -1) {
205         RTPP_ELOG(log, RTPP_LOG_ERR, "can't create timeout socket");
206         return;
207     }
208     if (rttp->local != NULL) {
209         if (bind(rttp->fd, rttp->local, SA_LEN(rttp->local)) < 0) {
210             RTPP_ELOG(log, RTPP_LOG_ERR, "can't bind timeout socket");
211             goto e0;
212         }
213     }
214     if (connect(rttp->fd, (struct sockaddr *)&(rttp->remote), rttp->remote_len) == -1) {
215         RTPP_ELOG(log, RTPP_LOG_ERR, "can't connect to timeout socket");
216         goto e0;
217     } else {
218         rttp->connected = 1;
219     }
220     return;
221 
222 e0:
223     close(rttp->fd);
224     rttp->fd = -1;
225     return;
226 }
227 
228 static void
do_timeout_notification(struct rtpp_notify_wi * wi,int retries,struct rtpp_log * log)229 do_timeout_notification(struct rtpp_notify_wi *wi, int retries,
230   struct rtpp_log *log)
231 {
232     int result;
233 
234     if (wi->rttp->connected == 0) {
235         reconnect_timeout_handler(log, wi->rttp);
236 
237         /* If connect fails, no notification will be sent */
238         if (wi->rttp->connected == 0) {
239             RTPP_LOG(log, RTPP_LOG_ERR, "unable to send timeout notification");
240             return;
241         }
242     }
243 
244     do {
245         result = send(wi->rttp->fd, wi->notify_buf, wi->len - 1, 0);
246     } while (result == -1 && errno == EINTR);
247 
248     if (result < 0) {
249         wi->rttp->connected = 0;
250         RTPP_ELOG(log, RTPP_LOG_ERR, "failed to send timeout notification");
251         if (retries > 0)
252             do_timeout_notification(wi, retries - 1, log);
253     }
254 }
255