3 Copyright (C) 2012 Belledonne Communications SARL
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.
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.
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.
23 #define UPNP_MAX_RETRY 4
25 UpnpPortBinding *upnp_port_binding_new();
26 UpnpPortBinding * upnp_port_binding_retain(UpnpPortBinding *port);
27 void upnp_port_binding_release(UpnpPortBinding *port);
29 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
30 int upnp_context_send_add_port_binding(LinphoneCore *lc, UpnpPortBinding *port);
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;
36 case UPNP_IGD_MESSAGE:
37 ortp_level = ORTP_MESSAGE;
39 case UPNP_IGD_WARNING:
40 ortp_level = ORTP_WARNING;
43 ortp_level = ORTP_ERROR;
48 ortp_logv(ortp_level, fmt, list);
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;
60 ms_mutex_lock(&lupnp->mutex);
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);
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;
75 // Emit add port binding
76 // Emit remove old port binding
77 lupnp->state = UPNP_Ok;
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);
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);
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);
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);
119 ms_mutex_unlock(&lupnp->mutex);
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;
127 if(port->state == UPNP_Idle) {
128 port->remote_port = -1;
130 port->state = UPNP_Pending;
132 if(port->retry >= UPNP_MAX_RETRY) {
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;
145 ret = upnp_igd_add_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
146 if(local_host != NULL) {
151 port->state = UPNP_Ko;
156 int upnp_context_send_remove_port_binding(LinphoneCore *lc, UpnpPortBinding *port) {
157 UpnpContext *lupnp = &lc->upnp;
158 upnp_igd_port_mapping mapping;
160 if(port->state == UPNP_Idle) {
162 port->state = UPNP_Pending;
164 if(port->retry >= UPNP_MAX_RETRY) {
167 mapping.cookie = upnp_port_binding_retain(port);
168 mapping.remote_port = port->remote_port;
169 mapping.remote_host = "";
170 mapping.protocol = port->protocol;
172 ret = upnp_igd_delete_port_mapping(lupnp->upnp_igd_ctxt, &mapping);
175 port->state = UPNP_Ko;
180 int upnp_call_process(LinphoneCall *call) {
181 LinphoneCore *lc = call->core;
182 UpnpContext *lupnp = &lc->upnp;
185 ms_mutex_lock(&lupnp->mutex);
186 // Don't handle when the call
187 if(lupnp->state != UPNP_Ko && call->upnp_session != NULL) {
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);
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);
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);
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);
230 ms_mutex_unlock(&lupnp->mutex);
234 int linphone_core_update_upnp(LinphoneCore *lc, LinphoneCall *call) {
235 return upnp_call_process(call);
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;
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;
249 lupnp->sip_udp = NULL;
251 if(transport.tcp_port != 0) {
252 lupnp->sip_tcp = upnp_port_binding_new();
253 lupnp->sip_tcp->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
255 lupnp->sip_tcp = NULL;
257 if(transport.tls_port != 0) {
258 lupnp->sip_tls = upnp_port_binding_new();
259 lupnp->sip_tls->protocol = UPNP_IGD_IP_PROTOCOL_TCP;
261 lupnp->sip_tls = NULL;
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");
270 lupnp->state = UPNP_Pending;
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);
280 if(lupnp->sip_tcp != NULL) {
281 upnp_port_binding_release(lupnp->sip_tcp);
283 if(lupnp->sip_tls != NULL) {
284 upnp_port_binding_release(lupnp->sip_tls);
286 if(lupnp->upnp_igd_ctxt != NULL) {
287 upnp_igd_destroy(lupnp->upnp_igd_ctxt);
289 ms_mutex_destroy(&lupnp->mutex);
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;
303 UpnpPortBinding *upnp_port_binding_retain(UpnpPortBinding *port) {
304 ms_mutex_lock(&port->mutex);
306 ms_mutex_unlock(&port->mutex);
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);
318 ms_mutex_unlock(&port->mutex);
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;
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);