2 upnp.c -- UPnP-IGD client
3 Copyright (C) 2015-2022 Guus Sliepen <guus@tinc-vpn.org>,
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.
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 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.
24 #include "miniupnpc/miniupnpc.h"
25 #include "miniupnpc/upnpcommands.h"
26 #include "miniupnpc/upnperrors.h"
37 static int upnp_discover_wait = 5;
38 static int upnp_refresh_period = 60;
40 // Unfortunately, libminiupnpc devs don't seem to care about API compatibility,
41 // and there are slight changes to function signatures between library versions.
42 // Well, at least they publish a "MINIUPNPC_API_VERSION" constant, so we got that going for us, which is nice.
43 // Differences between API versions are documented in "apiversions.txt" in the libminiupnpc distribution.
45 #ifndef MINIUPNPC_API_VERSION
46 #define MINIUPNPC_API_VERSION 0
49 static struct UPNPDev *upnp_discover(int delay, int *error) {
50 #if MINIUPNPC_API_VERSION <= 13
52 #if MINIUPNPC_API_VERSION < 8
53 #warning "The version of libminiupnpc you're building against seems to be too old. Expect trouble."
56 return upnpDiscover(delay, NULL, NULL, false, false, error);
58 #elif MINIUPNPC_API_VERSION <= 14
60 return upnpDiscover(delay, NULL, NULL, false, false, 2, error);
64 #if MINIUPNPC_API_VERSION > 17
65 #warning "The version of libminiupnpc you're building against seems to be too recent. Expect trouble."
68 return upnpDiscover(delay, NULL, NULL, UPNP_LOCAL_PORT_ANY, false, 2, error);
73 static void upnp_add_mapping(struct UPNPUrls *urls, struct IGDdatas *data, const char *myaddr, int socket, const char *proto) {
74 // Extract the port from the listening socket.
75 // Note that we can't simply use listen_socket[].sa because this won't have the port
76 // if we're running with Port=0 (dynamically assigned port).
78 socklen_t salen = sizeof(sa);
80 if(getsockname(socket, &sa.sa, &salen)) {
81 logger(DEBUG_PROTOCOL, LOG_ERR, "[upnp] Unable to get socket address: [%d] %s", sockerrno, sockstrerror(sockerrno));
86 sockaddr2str(&sa, NULL, &port);
89 logger(DEBUG_PROTOCOL, LOG_ERR, "[upnp] Unable to get socket port");
93 // Use a lease twice as long as the refresh period so that the mapping won't expire before we refresh.
94 char lease_duration[16];
95 snprintf(lease_duration, sizeof(lease_duration), "%d", upnp_refresh_period * 2);
97 int error = UPNP_AddPortMapping(urls->controlURL, data->first.servicetype, port, port, myaddr, identname, proto, NULL, lease_duration);
100 logger(DEBUG_PROTOCOL, LOG_INFO, "[upnp] Successfully set port mapping (%s:%s %s for %s seconds)", myaddr, port, proto, lease_duration);
102 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));
108 static void upnp_refresh(void) {
109 logger(DEBUG_PROTOCOL, LOG_INFO, "[upnp] Discovering IGD devices");
112 struct UPNPDev *devices = upnp_discover(upnp_discover_wait * 1000, &error);
115 logger(DEBUG_PROTOCOL, LOG_WARNING, "[upnp] Unable to find IGD devices: [%d] %s", error, strupnperror(error));
116 freeUPNPDevlist(devices);
120 struct UPNPUrls urls;
122 struct IGDdatas data;
126 int result = UPNP_GetValidIGD(devices, &urls, &data, myaddr, sizeof(myaddr));
129 logger(DEBUG_PROTOCOL, LOG_WARNING, "[upnp] No IGD found");
130 freeUPNPDevlist(devices);
134 logger(DEBUG_PROTOCOL, LOG_INFO, "[upnp] IGD found: [%d] %s (local address: %s, service type: %s)", result, urls.controlURL, myaddr, data.first.servicetype);
136 for(int i = 0; i < listen_sockets; i++) {
138 upnp_add_mapping(&urls, &data, myaddr, listen_socket[i].tcp.fd, "TCP");
142 upnp_add_mapping(&urls, &data, myaddr, listen_socket[i].udp.fd, "UDP");
147 freeUPNPDevlist(devices);
150 static void *upnp_thread(void *data) {
154 time_t start = time(NULL);
157 // Make sure we'll stick to the refresh period no matter how long upnp_refresh() takes.
158 time_t refresh_time = start + upnp_refresh_period;
159 time_t now = time(NULL);
161 if(now < refresh_time) {
162 sleep_millis((refresh_time - now) * 1000);
166 // TODO: we don't have a clean thread shutdown procedure, so we can't remove the mapping.
167 // this is probably not a concern as long as the UPnP device honors the lease duration,
168 // but considering how bug-riddled these devices often are, that's a big "if".
172 void upnp_init(bool tcp, bool udp) {
176 get_config_int(lookup_config(&config_tree, "UPnPDiscoverWait"), &upnp_discover_wait);
177 get_config_int(lookup_config(&config_tree, "UPnPRefreshPeriod"), &upnp_refresh_period);
180 int error = pthread_create(&thread, NULL, upnp_thread, NULL);
183 logger(DEBUG_ALWAYS, LOG_ERR, "Unable to start UPnP-IGD client thread: [%d] %s", error, strerror(error));