]> sjero.net Git - linphone/blob - coreapi/upnp.c
uPnP in progress
[linphone] / coreapi / upnp.c
1 /*
2 linphone
3 Copyright (C) 2012  Belledonne Communications SARL
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 */
19
20 #include "upnp.h"
21 #include "private.h"
22
23 #define UPNP_MAX_RETRY 4
24
25 UpnpPortBinding *upnp_port_binding_new();
26 UpnpPortBinding * upnp_port_binding_retain(UpnpPortBinding *port);
27 void upnp_port_binding_release(UpnpPortBinding *port);
28
29 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
30 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
31
32 /* Convert uPnP IGD logs to ortp logs */
33 void linphone_upnp_igd_print(void *cookie, upnp_igd_print_level level, const char *fmt, va_list list) {
34         int ortp_level = ORTP_DEBUG;
35         switch(level) {
36         case UPNP_IGD_MESSAGE:
37                 ortp_level = ORTP_MESSAGE;
38                 break;
39         case UPNP_IGD_WARNING:
40                 ortp_level = ORTP_WARNING;
41                 break;
42         case UPNP_IGD_ERROR:
43                 ortp_level = ORTP_ERROR;
44                 break;
45         default:
46                 break;
47         }
48         ortp_logv(ortp_level, fmt, list);
49 }
50
51 void linphone_upnp_igd_callback(void *cookie, upnp_igd_event event, void *arg) {
52         LinphoneCore *lc = (LinphoneCore *)cookie;
53         UpnpContext *lupnp = &lc->upnp;
54         upnp_igd_port_mapping *mapping = NULL;
55         UpnpPortBinding *port_mapping = NULL;
56         const char *ip_address = NULL;
57         const char *connection_status = NULL;
58         bool_t nat_enabled = FALSE;
59
60         ms_mutex_lock(&lupnp->mutex);
61
62         switch(event) {
63         case UPNP_IGD_EXTERNAL_IPADDRESS_CHANGED:
64         case UPNP_IGD_NAT_ENABLED_CHANGED:
65         case UPNP_IGD_CONNECTION_STATUS_CHANGED:
66                 ip_address = upnp_igd_get_external_ipaddress(lupnp->upnp_igd_ctxt);
67                 connection_status = upnp_igd_get_connection_status(lupnp->upnp_igd_ctxt);
68                 nat_enabled = upnp_igd_get_nat_enabled(lupnp->upnp_igd_ctxt);
69
70                 if(ip_address == NULL || connection_status == NULL) {
71                         lupnp->state = UPNP_Pending;
72                 } else if(strcasecmp(connection_status, "Connected")  || !nat_enabled) {
73                         lupnp->state = UPNP_Ko;
74                 } else {
75                         // Emit add port binding
76                         // Emit remove old port binding
77                         lupnp->state = UPNP_Ok;
78                 }
79
80                 break;
81
82         case UPNP_IGD_PORT_MAPPING_ADD_SUCCESS:
83                 mapping = (upnp_igd_port_mapping *) arg;
84                 port_mapping = (UpnpPortBinding*) mapping->cookie;
85                 port_mapping->remote_port = mapping->remote_port;
86                 port_mapping->state = UPNP_Ok;
87                 // TODO: SAVE IN CONFIG THE PORT
88                 upnp_port_binding_release(port_mapping);
89                 break;
90
91         case UPNP_IGD_PORT_MAPPING_ADD_FAILURE:
92                 mapping = (upnp_igd_port_mapping *) arg;
93                 port_mapping = (UpnpPortBinding*) mapping->cookie;
94                 upnp_context_send_add_port_binding(lc, port_mapping);
95                 upnp_port_binding_release(port_mapping);
96                 break;
97
98         case UPNP_IGD_PORT_MAPPING_REMOVE_SUCCESS:
99                 mapping = (upnp_igd_port_mapping *) arg;
100                 port_mapping = (UpnpPortBinding*) mapping->cookie;
101                 port_mapping->remote_port = -1;
102                 port_mapping->state = UPNP_Idle;
103                 // TODO: REMOVE FROM CONFIG THE PORT
104                 upnp_port_binding_release(port_mapping);
105                 break;
106
107         case UPNP_IGD_PORT_MAPPING_REMOVE_FAILURE:
108                 mapping = (upnp_igd_port_mapping *) arg;
109                 port_mapping = (UpnpPortBinding*) mapping->cookie;
110                 upnp_context_send_remove_port_binding(lc, port_mapping);
111                 // TODO: REMOVE FROM CONFIG THE PORT (DON'T TRY ANYMORE)
112                 upnp_port_binding_release(port_mapping);
113                 break;
114
115         default:
116                 break;
117         }
118
119         ms_mutex_unlock(&lupnp->mutex);
120 }
121
122 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
123         UpnpContext *lupnp = &lc->upnp;
124         upnp_igd_port_mapping mapping;
125         char * local_host = NULL;
126         int ret;
127         if(port->state == UPNP_Idle) {
128                 port->remote_port = -1;
129                 port->retry = 0;
130                 port->state = UPNP_Pending;
131         }
132         if(port->retry >= UPNP_MAX_RETRY) {
133                 ret = -1;
134         } else {
135                 local_host = upnp_igd_get_local_ipaddress(lupnp->upnp_igd_ctxt);
136                 mapping.cookie = upnp_port_binding_retain(port);
137                 mapping.local_port = port->local_port;
138                 mapping.local_host = local_host;
139                 mapping.remote_port = rand()%1024 + 1024;
140                 mapping.remote_host = "";
141                 mapping.description = PACKAGE_NAME;
142                 mapping.protocol = port->protocol;
143
144                 port->retry++;
145                 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
146                 if(local_host != NULL) {
147                         free(local_host);
148                 }
149         }
150         if(ret != 0) {
151                 port->state = UPNP_Ko;
152         }
153         return ret;
154 }
155
156 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
157         UpnpContext *lupnp = &lc->upnp;
158         upnp_igd_port_mapping mapping;
159         int ret;
160         if(port->state == UPNP_Idle) {
161                 port->retry = 0;
162                 port->state = UPNP_Pending;
163         }
164         if(port->retry >= UPNP_MAX_RETRY) {
165                 ret = -1;
166         } else {
167                 mapping.cookie = upnp_port_binding_retain(port);
168                 mapping.remote_port = port->remote_port;
169                 mapping.remote_host = "";
170                 mapping.protocol = port->protocol;
171                 port->retry++;
172                 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
173         }
174         if(ret != 0) {
175                 port->state = UPNP_Ko;
176         }
177         return ret;
178 }
179
180 int upnp_call_process(LinphoneCall *call) {
181         LinphoneCore *lc = call->core;
182         UpnpContext *lupnp = &lc->upnp;
183         int ret = -1;
184
185         ms_mutex_lock(&lupnp->mutex);
186         // Don't handle when the call
187         if(lupnp->state != UPNP_Ko && call->upnp_session != NULL) {
188                 ret = 0;
189
190                 /*
191                  * Audio part
192                  */
193                 call->upnp_session->audio_rtp->local_port = call->audio_port;
194                 call->upnp_session->audio_rtcp->local_port = call->audio_port+1;
195                 if(call->upnp_session->audio_rtp->state == UPNP_Idle && call->audiostream != NULL) {
196                         // Add audio port binding
197                         upnp_context_send_add_port_binding(lc, call->upnp_session->audio_rtp);
198                 } else if(call->upnp_session->audio_rtp->state == UPNP_Ok && call->audiostream == NULL) {
199                         // Remove audio port binding
200                         upnp_context_send_remove_port_binding(lc, call->upnp_session->audio_rtp);
201                 }
202                 if(call->upnp_session->audio_rtcp->state == UPNP_Idle && call->audiostream != NULL) {
203                         // Add audio port binding
204                         upnp_context_send_add_port_binding(lc, call->upnp_session->audio_rtcp);
205                 } else if(call->upnp_session->audio_rtcp->state == UPNP_Ok && call->audiostream == NULL) {
206                         // Remove audio port binding
207                         upnp_context_send_remove_port_binding(lc, call->upnp_session->audio_rtcp);
208                 }
209
210                 /*
211                  * Video part
212                  */
213                 call->upnp_session->video_rtp->local_port = call->video_port;
214                 call->upnp_session->video_rtcp->local_port = call->video_port+1;
215                 if(call->upnp_session->video_rtp->state == UPNP_Idle && call->videostream != NULL) {
216                         // Add video port binding
217                         upnp_context_send_add_port_binding(lc, call->upnp_session->video_rtp);
218                 } else if(call->upnp_session->video_rtp->state == UPNP_Ok && call->videostream == NULL) {
219                         // Remove video port binding
220                         upnp_context_send_remove_port_binding(lc, call->upnp_session->video_rtp);
221                 }
222                 if(call->upnp_session->video_rtcp->state == UPNP_Idle && call->videostream != NULL) {
223                         // Add video port binding
224                         upnp_context_send_add_port_binding(lc, call->upnp_session->video_rtcp);
225                 } else if(call->upnp_session->video_rtcp->state == UPNP_Ok && call->videostream == NULL) {
226                         // Remove video port binding
227                         upnp_context_send_remove_port_binding(lc, call->upnp_session->video_rtcp);
228                 }
229         }
230         ms_mutex_unlock(&lupnp->mutex);
231         return ret;
232 }
233
234 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
235         return upnp_call_process(call);
236 }
237
238 int upnp_context_init(LinphoneCore *lc) {
239         LCSipTransports transport;
240         UpnpContext *lupnp = &lc->upnp;
241         ms_mutex_init(&lupnp->mutex, NULL);
242         lupnp->state = UPNP_Idle;
243
244         linphone_core_get_sip_transports(lc, &transport);
245         if(transport.udp_port != 0) {
246                 lupnp->sip_udp = upnp_port_binding_new();
247                 lupnp->sip_udp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
248         } else {
249                 lupnp->sip_udp = NULL;
250         }
251         if(transport.tcp_port != 0) {
252                 lupnp->sip_tcp = upnp_port_binding_new();
253                 lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
254         } else {
255                 lupnp->sip_tcp = NULL;
256         }
257         if(transport.tls_port != 0) {
258                 lupnp->sip_tls = upnp_port_binding_new();
259                 lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
260         } else {
261                 lupnp->sip_tls = NULL;
262         }
263         lupnp->upnp_igd_ctxt = NULL;
264         lupnp->upnp_igd_ctxt = upnp_igd_create(linphone_upnp_igd_callback, linphone_upnp_igd_print, lc);
265         if(lupnp->upnp_igd_ctxt == NULL ) {
266                 lupnp->state = UPNP_Ko;
267                 ms_error("Can't create uPnP IGD context");
268                 return -1;
269         }
270         lupnp->state = UPNP_Pending;
271         return 0;
272 }
273
274 void upnp_context_uninit(LinphoneCore *lc) {
275         // Emit remove port (sip & saved)
276         UpnpContext *lupnp = &lc->upnp;
277         if(lupnp->sip_udp != NULL) {
278                 upnp_port_binding_release(lupnp->sip_udp);
279         }
280         if(lupnp->sip_tcp != NULL) {
281                 upnp_port_binding_release(lupnp->sip_tcp);
282         }
283         if(lupnp->sip_tls != NULL) {
284                 upnp_port_binding_release(lupnp->sip_tls);
285         }
286         if(lupnp->upnp_igd_ctxt != NULL) {
287                 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
288         }
289         ms_mutex_destroy(&lupnp->mutex);
290 }
291
292 UpnpPortBinding *upnp_port_binding_new() {
293         UpnpPortBinding *port = NULL;
294         port = ms_new0(UpnpPortBinding,1);
295         ms_mutex_init(&port->mutex, NULL);
296         port->state = UPNP_Idle;
297         port->local_port = -1;
298         port->remote_port = -1;
299         port->ref = 1;
300         return port;
301 }
302
303 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
304         ms_mutex_lock(&port->mutex);
305         port->ref++;
306         ms_mutex_unlock(&port->mutex);
307         return port;
308 }
309
310 void upnp_port_binding_release(UpnpPortBinding *port) {
311         ms_mutex_lock(&port->mutex);
312         if(--port->ref == 0) {
313                 ms_mutex_unlock(&port->mutex);
314                 ms_mutex_destroy(&port->mutex);
315                 ms_free(port);
316                 return;
317         }
318         ms_mutex_unlock(&port->mutex);
319 }
320
321 UpnpSession* upnp_session_new() {
322         UpnpSession *session = ms_new0(UpnpSession,1);
323         session->state = UPNP_Idle;
324         session->audio_rtp = upnp_port_binding_new();
325         session->audio_rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
326         session->audio_rtcp = upnp_port_binding_new();
327         session->audio_rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
328         session->video_rtp = upnp_port_binding_new();
329         session->video_rtp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
330         session->video_rtcp = upnp_port_binding_new();
331         session->video_rtcp->protocol = UPNP_IGD_IP_PROTOCOL_UDP;
332         return NULL;
333 }
334
335 void upnp_session_destroy(UpnpSession* session) {
336         upnp_port_binding_release(session->audio_rtp);
337         upnp_port_binding_release(session->audio_rtcp);
338         upnp_port_binding_release(session->video_rtp);
339         upnp_port_binding_release(session->video_rtp);
340         // TODO: send remove
341         ms_free(session);
342 }