initial NAT lib commit (UPnP and NAT-PMP support)
[oweals/gnunet.git] / src / nat / natpmp.c
1 /*
2      This file is part of GNUnet.
3      (C) 2009 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /*
22  * This file has been adapted from the Transmission project:
23  * Originally licensed by the GPL version 2.
24  * Copyright (C) 2007-2009 Charles Kerr <charles@transmissionbt.com>
25  */
26
27 /**
28  * @file nat/natpmp.c
29  * @brief NAT-PMP support for the NAT library
30  *
31  * @author Milan Bouchet-Valat
32  */
33
34 #ifdef __cplusplus
35 extern "C"
36 {
37 #if 0                           /* keep Emacsens' auto-indent happy */
38 }
39 #endif
40 #endif
41
42 #include <assert.h>
43 #include <errno.h>
44 #include <time.h>
45 #include <inttypes.h>
46
47 #define ENABLE_STRNATPMPERR
48 #include <libnatpmp/natpmp.h>
49
50 #include "platform.h"
51 #include "gnunet_common.h"
52 #include "gnunet_nat_lib.h"
53 #include "natpmp.h"
54
55 #define LIFETIME_SECS 3600
56 #define COMMAND_WAIT_SECS 8
57 /* Component name for logging */
58 #define COMP_NAT_NATPMP _("NAT (NAT-PMP))")
59
60 typedef enum
61 {
62   NATPMP_IDLE,
63   NATPMP_ERR,
64   NATPMP_DISCOVER,
65   NATPMP_RECV_PUB,
66   NATPMP_SEND_MAP,
67   NATPMP_RECV_MAP,
68   NATPMP_SEND_UNMAP,
69   NATPMP_RECV_UNMAP
70 }
71 NATPMP_state;
72
73 struct GNUNET_NAT_NATPMP_Handle
74 {
75   const struct sockaddr *addr;
76   socklen_t addrlen;
77   int is_mapped;
78   int has_discovered;
79   int port;
80   time_t renew_time;
81   time_t command_time;
82   NATPMP_state state;
83   natpmp_t natpmp;
84 };
85
86 /**
87 ***
88 **/
89
90 static void
91 log_val (const char *func, int ret)
92 {
93 #ifdef DEBUG
94   if (ret == NATPMP_TRYAGAIN)
95     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_NATPMP, _("%s retry (%d)\n"), func, ret);
96 //    return;
97   if (ret >= 0)
98     GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_NATPMP, _("%s succeeded (%d)\n"), func, ret);
99   else
100     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_NATPMP,
101                 "%s failed.  natpmp returned %d (%s); errno is %d (%s)\n",
102                 func, ret, strnatpmperr (ret), errno, strerror (errno));
103 #else
104   return;
105 #endif
106 }
107
108 struct GNUNET_NAT_NATPMP_Handle *
109 GNUNET_NAT_NATPMP_init (const struct sockaddr *addr, socklen_t addrlen,
110                         u_short port)
111 {
112   struct GNUNET_NAT_NATPMP_Handle *nat;
113
114   nat = GNUNET_malloc (sizeof (struct GNUNET_NAT_NATPMP_Handle));
115   nat->state = NATPMP_DISCOVER;
116   nat->port = port;
117   nat->addr = addr;
118   nat->addrlen = addrlen;
119   return nat;
120 }
121
122 void
123 GNUNET_NAT_NATPMP_close (GNUNET_NAT_NATPMP_Handle * nat)
124 {
125   if (nat)
126     {
127       closenatpmp (&nat->natpmp);
128       GNUNET_free (nat);
129     }
130 }
131
132 static int
133 can_send_command (const struct GNUNET_NAT_NATPMP_Handle *nat)
134 {
135   return time (NULL) >= nat->command_time;
136 }
137
138 static void
139 set_command_time (struct GNUNET_NAT_NATPMP_Handle *nat)
140 {
141   nat->command_time = time (NULL) + COMMAND_WAIT_SECS;
142 }
143
144 int
145 GNUNET_NAT_NATPMP_pulse (struct GNUNET_NAT_NATPMP_Handle *nat, int is_enabled,
146                          struct sockaddr **ext_addr)
147 {
148   int ret;
149
150   /* Keep to NULL if address could not be found */
151   *ext_addr = NULL;
152
153   if (is_enabled && (nat->state == NATPMP_DISCOVER))
154     {
155       int val = initnatpmp (&nat->natpmp);
156       log_val ("initnatpmp", val);
157       val = sendpublicaddressrequest (&nat->natpmp);
158       log_val ("sendpublicaddressrequest", val);
159       nat->state = val < 0 ? NATPMP_ERR : NATPMP_RECV_PUB;
160       nat->has_discovered = 1;
161       set_command_time (nat);
162     }
163
164   if ((nat->state == NATPMP_RECV_PUB) && can_send_command (nat))
165     {
166       natpmpresp_t response;
167       const int val = readnatpmpresponseorretry (&nat->natpmp,
168                                                  &response);
169       log_val ("readnatpmpresponseorretry", val);
170       if (val >= 0)
171         {
172           *ext_addr =
173             GNUNET_malloc (sizeof (response.pnu.publicaddress.addr));
174           memcpy (*ext_addr, &response.pnu.publicaddress.addr,
175                   (sizeof (response.pnu.publicaddress.addr)));
176 #ifdef DEBUG
177           GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG, COMP_NAT_NATPMP,
178                       _("Found public IP address %s\n"),
179                       GNUNET_a2s (*ext_addr, sizeof (*ext_addr)));
180 #endif
181           nat->state = NATPMP_IDLE;
182         }
183       else if (val != NATPMP_TRYAGAIN)
184         {
185           nat->state = NATPMP_ERR;
186         }
187     }
188
189   if ((nat->state == NATPMP_IDLE) || (nat->state == NATPMP_ERR))
190     {
191       if (nat->is_mapped && !is_enabled)
192         nat->state = NATPMP_SEND_UNMAP;
193     }
194
195   if ((nat->state == NATPMP_SEND_UNMAP) && can_send_command (nat))
196     {
197       const int val =
198         sendnewportmappingrequest (&nat->natpmp, NATPMP_PROTOCOL_TCP,
199                                    nat->port, nat->port,
200                                    0);
201       log_val ("sendnewportmappingrequest", val);
202       nat->state = val < 0 ? NATPMP_ERR : NATPMP_RECV_UNMAP;
203       set_command_time (nat);
204     }
205
206   if (nat->state == NATPMP_RECV_UNMAP)
207     {
208       natpmpresp_t resp;
209       const int val = readnatpmpresponseorretry (&nat->natpmp, &resp);
210       log_val ("readnatpmpresponseorretry", val);
211       if (val >= 0)
212         {
213           const int p = resp.pnu.newportmapping.privateport;
214           GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_NATPMP,
215                       _("No longer forwarding port %d\n"), p);
216           if (nat->port == p)
217             {
218               nat->port = -1;
219               nat->state = NATPMP_IDLE;
220               nat->is_mapped = 0;
221             }
222         }
223       else if (val != NATPMP_TRYAGAIN)
224         {
225           nat->state = NATPMP_ERR;
226         }
227     }
228
229   if (nat->state == NATPMP_IDLE)
230     {
231       if (is_enabled && !nat->is_mapped && nat->has_discovered)
232         nat->state = NATPMP_SEND_MAP;
233
234       else if (nat->is_mapped && time (NULL) >= nat->renew_time)
235         nat->state = NATPMP_SEND_MAP;
236     }
237
238   if ((nat->state == NATPMP_SEND_MAP) && can_send_command (nat))
239     {
240       const int val =
241         sendnewportmappingrequest (&nat->natpmp, NATPMP_PROTOCOL_TCP,
242                                    nat->port,
243                                    nat->port,
244                                    LIFETIME_SECS);
245       log_val ("sendnewportmappingrequest", val);
246       nat->state = val < 0 ? NATPMP_ERR : NATPMP_RECV_MAP;
247       set_command_time (nat);
248     }
249
250   if (nat->state == NATPMP_RECV_MAP)
251     {
252       natpmpresp_t resp;
253       const int val = readnatpmpresponseorretry (&nat->natpmp, &resp);
254       log_val ("readnatpmpresponseorretry", val);
255       if (val >= 0)
256         {
257           nat->state = NATPMP_IDLE;
258           nat->is_mapped = 1;
259           nat->renew_time = time (NULL) + LIFETIME_SECS;
260           nat->port = resp.pnu.newportmapping.privateport;
261           GNUNET_log_from (GNUNET_ERROR_TYPE_INFO, COMP_NAT_NATPMP,
262                       _("Port %d forwarded successfully\n"), nat->port);
263         }
264       else if (val != NATPMP_TRYAGAIN)
265         {
266           nat->state = NATPMP_ERR;
267         }
268     }
269
270   switch (nat->state)
271     {
272     case NATPMP_IDLE:
273       ret =
274         nat->is_mapped ? GNUNET_NAT_PORT_MAPPED : GNUNET_NAT_PORT_UNMAPPED;
275       break;
276
277     case NATPMP_DISCOVER:
278       ret = GNUNET_NAT_PORT_UNMAPPED;
279       break;
280
281     case NATPMP_RECV_PUB:
282     case NATPMP_SEND_MAP:
283     case NATPMP_RECV_MAP:
284       ret = GNUNET_NAT_PORT_MAPPING;
285       break;
286
287     case NATPMP_SEND_UNMAP:
288     case NATPMP_RECV_UNMAP:
289       ret = GNUNET_NAT_PORT_UNMAPPING;
290       break;
291
292     default:
293       ret = GNUNET_NAT_PORT_ERROR;
294       break;
295     }
296   return ret;
297 }