invitation.c: fix socket error checking on Windows
[tinc] / src / upnp.c
1 /*
2     upnp.c -- UPnP-IGD client
3     Copyright (C) 2015-2018 Guus Sliepen <guus@tinc-vpn.org>,
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (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 along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "upnp.h"
21
22 #ifndef HAVE_MINGW
23 #include <pthread.h>
24 #endif
25
26 #include "miniupnpc/miniupnpc.h"
27 #include "miniupnpc/upnpcommands.h"
28 #include "miniupnpc/upnperrors.h"
29
30 #include "system.h"
31 #include "logger.h"
32 #include "names.h"
33 #include "net.h"
34 #include "netutl.h"
35 #include "utils.h"
36
37 static bool upnp_tcp;
38 static bool upnp_udp;
39 static int upnp_discover_wait = 5;
40 static int upnp_refresh_period = 60;
41
42 // Unfortunately, libminiupnpc devs don't seem to care about API compatibility,
43 // and there are slight changes to function signatures between library versions.
44 // Well, at least they publish a "MINIUPNPC_API_VERSION" constant, so we got that going for us, which is nice.
45 // Differences between API versions are documented in "apiversions.txt" in the libminiupnpc distribution.
46
47 #ifndef MINIUPNPC_API_VERSION
48 #define MINIUPNPC_API_VERSION 0
49 #endif
50
51 static struct UPNPDev *upnp_discover(int delay, int *error) {
52 #if MINIUPNPC_API_VERSION <= 13
53
54 #if MINIUPNPC_API_VERSION < 8
55 #warning "The version of libminiupnpc you're building against seems to be too old. Expect trouble."
56 #endif
57
58         return upnpDiscover(delay, NULL, NULL, false, false, error);
59
60 #elif MINIUPNPC_API_VERSION <= 14
61
62         return upnpDiscover(delay, NULL, NULL, false, false, 2, error);
63
64 #else
65
66 #if MINIUPNPC_API_VERSION > 17
67 #warning "The version of libminiupnpc you're building against seems to be too recent. Expect trouble."
68 #endif
69
70         return upnpDiscover(delay, NULL, NULL, UPNP_LOCAL_PORT_ANY, false, 2, error);
71
72 #endif
73 }
74
75 static void upnp_add_mapping(struct UPNPUrls *urls, struct IGDdatas *data, const char *myaddr, int socket, const char *proto) {
76         // Extract the port from the listening socket.
77         // Note that we can't simply use listen_socket[].sa because this won't have the port
78         // if we're running with Port=0 (dynamically assigned port).
79         sockaddr_t sa;
80         socklen_t salen = sizeof(sa);
81
82         if(getsockname(socket, &sa.sa, &salen)) {
83                 logger(DEBUG_PROTOCOL, LOG_ERR, "[upnp] Unable to get socket address: [%d] %s", sockerrno, sockstrerror(sockerrno));
84                 return;
85         }
86
87         char *port;
88         sockaddr2str(&sa, NULL, &port);
89
90         if(!port) {
91                 logger(DEBUG_PROTOCOL, LOG_ERR, "[upnp] Unable to get socket port");
92                 return;
93         }
94
95         // Use a lease twice as long as the refresh period so that the mapping won't expire before we refresh.
96         char lease_duration[16];
97         snprintf(lease_duration, sizeof(lease_duration), "%d", upnp_refresh_period * 2);
98
99         int error = UPNP_AddPortMapping(urls->controlURL, data->first.servicetype, port, port, myaddr, identname, proto, NULL, lease_duration);
100
101         if(error == 0) {
102                 logger(DEBUG_PROTOCOL, LOG_INFO, "[upnp] Successfully set port mapping (%s:%s %s for %s seconds)", myaddr, port, proto, lease_duration);
103         } else {
104                 logger(DEBUG_PROTOCOL, LOG_ERR, "[upnp] Failed to set port mapping (%s:%s %s for %s seconds): [%d] %s", myaddr, port, proto, lease_duration, error, strupnperror(error));
105         }
106
107         free(port);
108 }
109
110 static void upnp_refresh() {
111         logger(DEBUG_PROTOCOL, LOG_INFO, "[upnp] Discovering IGD devices");
112
113         int error;
114         struct UPNPDev *devices = upnp_discover(upnp_discover_wait * 1000, &error);
115
116         if(!devices) {
117                 logger(DEBUG_PROTOCOL, LOG_WARNING, "[upnp] Unable to find IGD devices: [%d] %s", error, strupnperror(error));
118                 freeUPNPDevlist(devices);
119                 return;
120         }
121
122         struct UPNPUrls urls;
123
124         struct IGDdatas data;
125
126         char myaddr[64];
127
128         int result = UPNP_GetValidIGD(devices, &urls, &data, myaddr, sizeof(myaddr));
129
130         if(result <= 0) {
131                 logger(DEBUG_PROTOCOL, LOG_WARNING, "[upnp] No IGD found");
132                 freeUPNPDevlist(devices);
133                 return;
134         }
135
136         logger(DEBUG_PROTOCOL, LOG_INFO, "[upnp] IGD found: [%d] %s (local address: %s, service type: %s)", result, urls.controlURL, myaddr, data.first.servicetype);
137
138         for(int i = 0; i < listen_sockets; i++) {
139                 if(upnp_tcp) {
140                         upnp_add_mapping(&urls, &data, myaddr, listen_socket[i].tcp.fd, "TCP");
141                 }
142
143                 if(upnp_udp) {
144                         upnp_add_mapping(&urls, &data, myaddr, listen_socket[i].udp.fd, "UDP");
145                 }
146         }
147
148         FreeUPNPUrls(&urls);
149         freeUPNPDevlist(devices);
150 }
151
152 static void *upnp_thread(void *data) {
153         (void)data;
154
155         while(true) {
156                 time_t start = time(NULL);
157                 upnp_refresh();
158
159                 // Make sure we'll stick to the refresh period no matter how long upnp_refresh() takes.
160                 time_t refresh_time = start + upnp_refresh_period;
161                 time_t now = time(NULL);
162
163                 if(now < refresh_time) {
164                         nanosleep(&(struct timespec) {
165                                 refresh_time - now, 0
166                         }, NULL);
167                 }
168         }
169
170         // TODO: we don't have a clean thread shutdown procedure, so we can't remove the mapping.
171         //       this is probably not a concern as long as the UPnP device honors the lease duration,
172         //       but considering how bug-riddled these devices often are, that's a big "if".
173         return NULL;
174 }
175
176 void upnp_init(bool tcp, bool udp) {
177         upnp_tcp = tcp;
178         upnp_udp = udp;
179
180         get_config_int(lookup_config(config_tree, "UPnPDiscoverWait"), &upnp_discover_wait);
181         get_config_int(lookup_config(config_tree, "UPnPRefreshPeriod"), &upnp_refresh_period);
182
183 #ifdef HAVE_MINGW
184         HANDLE handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)upnp_thread, NULL, 0, NULL);
185
186         if(!handle) {
187                 logger(DEBUG_ALWAYS, LOG_ERR, "Unable to start UPnP-IGD client thread");
188         }
189
190 #else
191         pthread_t thread;
192         int error = pthread_create(&thread, NULL, upnp_thread, NULL);
193
194         if(error) {
195                 logger(DEBUG_ALWAYS, LOG_ERR, "Unable to start UPnP-IGD client thread: [%d] %s", error, strerror(error));
196         }
197
198 #endif
199 }