Sets the socket send buffer size for the UDP socket, in bytes.
If set to zero, the default buffer size will be used by the operating system.
Note: this setting can have a significant impact on performance, especially raw throughput.
+.It Va UPnP Li = yes | udponly | no Po no Pc
+If this option is enabled then tinc will search for UPnP-IGD devices on the local network.
+It will then create and maintain port mappings for tinc's listening TCP and UDP ports.
+If set to "udponly", tinc will only create a mapping for its UDP (data) port, not for its TCP (metaconnection) port.
+Note that tinc must have been built with miniupnpc support for this feature to be available.
+Furthermore, be advised that enabling this can have security implications, because the miniupnpc library that
+tinc uses might not be well-hardened with regard to malicious UPnP replies.
+.It Va UPnPDiscoverWait Li = Ar seconds Pq 5
+The amount of time to wait for replies when probing the local network for UPnP devices.
+.It Va UPnPRefreshPeriod Li = Ar seconds Pq 60
+How often tinc will re-add the port mapping, in case it gets reset on the UPnP device. This also controls the duration of the port mapping itself, which will be set to twice that duration.
.El
.Sh HOST CONFIGURATION FILES
The host configuration files contain all information needed
If set to zero, the default buffer size will be used by the operating system.
Note: this setting can have a significant impact on performance, especially raw throughput.
+@cindex UPnP
+@item UPnP = <yes|udponly|no> (no)
+If this option is enabled then tinc will search for UPnP-IGD devices on the local network.
+It will then create and maintain port mappings for tinc's listening TCP and UDP ports.
+If set to "udponly", tinc will only create a mapping for its UDP (data) port, not for its TCP (metaconnection) port.
+Note that tinc must have been built with miniupnpc support for this feature to be available.
+Furthermore, be advised that enabling this can have security implications, because the miniupnpc library that
+tinc uses might not be well-hardened with regard to malicious UPnP replies.
+
+@cindex UPnPDiscoverWait
+@item UPnPDiscoverWait = <seconds> (5)
+The amount of time to wait for replies when probing the local network for UPnP devices.
+
+@cindex UPnPRefreshPeriod
+@item UPnPRefreshPeriod = <seconds> (5)
+How often tinc will re-add the port mapping, in case it gets reset on the UPnP device.
+This also controls the duration of the port mapping itself, which will be set to twice that duration.
+
@end table
--- /dev/null
+/*
+ upnp.c -- UPnP-IGD client
+ Copyright (C) 2015 Guus Sliepen <guus@tinc-vpn.org>,
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "upnp.h"
+
+#include <pthread.h>
+
+#include "miniupnpc/miniupnpc.h"
+#include "miniupnpc/upnpcommands.h"
+#include "miniupnpc/upnperrors.h"
+
+#include "system.h"
+#include "logger.h"
+#include "names.h"
+#include "net.h"
+#include "netutl.h"
+#include "utils.h"
+
+static bool upnp_tcp;
+static bool upnp_udp;
+static int upnp_discover_wait = 5;
+static int upnp_refresh_period = 60;
+
+static void upnp_add_mapping(struct UPNPUrls *urls, struct IGDdatas *data, const char *myaddr, int socket, const char *proto) {
+ // Extract the port from the listening socket.
+ // Note that we can't simply use listen_socket[].sa because this won't have the port
+ // if we're running with Port=0 (dynamically assigned port).
+ sockaddr_t sa;
+ socklen_t salen = sizeof sa;
+ if (getsockname(socket, &sa.sa, &salen)) {
+ logger(DEBUG_PROTOCOL, LOG_ERR, "[upnp] Unable to get socket address: [%d] %s", sockerrno, sockstrerror(sockerrno));
+ return;
+ }
+ char *port;
+ sockaddr2str(&sa, NULL, &port);
+ if (!port) {
+ logger(DEBUG_PROTOCOL, LOG_ERR, "[upnp] Unable to get socket port");
+ return;
+ }
+
+ // Use a lease twice as long as the refresh period so that the mapping won't expire before we refresh.
+ char lease_duration[16];
+ snprintf(lease_duration, sizeof lease_duration, "%d", upnp_refresh_period * 2);
+
+ int error = UPNP_AddPortMapping(urls->controlURL, data->first.servicetype, port, port, myaddr, identname, proto, NULL, lease_duration);
+ if (error == 0) {
+ logger(DEBUG_PROTOCOL, LOG_INFO, "[upnp] Successfully set port mapping (%s:%s %s for %s seconds)", myaddr, port, proto, lease_duration);
+ } else {
+ 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));
+ }
+
+ free(port);
+}
+
+static void upnp_refresh() {
+ logger(DEBUG_PROTOCOL, LOG_INFO, "[upnp] Discovering IGD devices");
+
+ int error;
+ struct UPNPDev *devices = upnpDiscover(upnp_discover_wait * 1000, NULL, NULL, false, false, &error);
+ if (!devices) {
+ logger(DEBUG_PROTOCOL, LOG_WARNING, "[upnp] Unable to find IGD devices: [%d] %s", error, strupnperror(error));
+ freeUPNPDevlist(devices);
+ return;
+ }
+
+ struct UPNPUrls urls;
+ struct IGDdatas data;
+ char myaddr[64];
+ int result = UPNP_GetValidIGD(devices, &urls, &data, myaddr, sizeof myaddr);
+ if (result <= 0) {
+ logger(DEBUG_PROTOCOL, LOG_WARNING, "[upnp] No IGD found");
+ freeUPNPDevlist(devices);
+ return;
+ }
+ logger(DEBUG_PROTOCOL, LOG_INFO, "[upnp] IGD found: [%d] %s (local address: %s, service type: %s)", result, urls.controlURL, myaddr, data.first.servicetype);
+
+ for (int i = 0; i < listen_sockets; i++) {
+ if (upnp_tcp) upnp_add_mapping(&urls, &data, myaddr, listen_socket[i].tcp.fd, "TCP");
+ if (upnp_udp) upnp_add_mapping(&urls, &data, myaddr, listen_socket[i].udp.fd, "UDP");
+ }
+
+ FreeUPNPUrls(&urls);
+ freeUPNPDevlist(devices);
+}
+
+static void *upnp_thread(void *data) {
+ while (true) {
+ time_t start = time(NULL);
+ upnp_refresh();
+
+ // Make sure we'll stick to the refresh period no matter how long upnp_refresh() takes.
+ time_t refresh_time = start + upnp_refresh_period;
+ time_t now = time(NULL);
+ if (now < refresh_time) sleep(refresh_time - now);
+ }
+
+ // TODO: we don't have a clean thread shutdown procedure, so we can't remove the mapping.
+ // this is probably not a concern as long as the UPnP device honors the lease duration,
+ // but considering how bug-riddled these devices often are, that's a big "if".
+ return NULL;
+}
+
+void upnp_init(bool tcp, bool udp) {
+ upnp_tcp = tcp;
+ upnp_udp = udp;
+
+ get_config_int(lookup_config(config_tree, "UPnPDiscoverWait"), &upnp_discover_wait);
+ get_config_int(lookup_config(config_tree, "UPnPRefreshPeriod"), &upnp_refresh_period);
+
+ pthread_t thread;
+ int error = pthread_create(&thread, NULL, upnp_thread, NULL);
+ if (error) {
+ logger(DEBUG_ALWAYS, LOG_ERR, "Unable to start UPnP-IGD client thread: [%d] %s", error, strerror(error));
+ }
+}