/*
This file is part of GNUnet.
Copyright (C) 2009, 2015, 2016, 2017 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
GNUnet 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
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
SPDX-License-Identifier: AGPL3.0-or-later
*/
/**
* Code to figure out what our external IPv4 address(es) might
* be (external IPv4s are what is seen on the rest of the Internet).
*
* This can be implemented using different methods, and we allow
* the main service to be notified about changes to what we believe
* is our external IPv4 address.
*
* Note that this is explicitly only about NATed systems; if one
* of our network interfaces has a global IP address this does
* not count as "external".
*
* @file nat/gnunet-service-nat_externalip.c
* @brief Functions for monitoring external IPv4 addresses
* @author Christian Grothoff
*/
#include "platform.h"
#include
#include "gnunet_util_lib.h"
#include "gnunet_protocols.h"
#include "gnunet_signatures.h"
#include "gnunet_statistics_service.h"
#include "gnunet_resolver_service.h"
#include "gnunet_nat_service.h"
#include "gnunet-service-nat.h"
#include "gnunet-service-nat_externalip.h"
#include "gnunet-service-nat_stun.h"
#include "gnunet-service-nat_mini.h"
#include "gnunet-service-nat_helper.h"
#include "nat.h"
#include
/**
* How long do we wait until we re-try running `external-ip` if the
* command failed to terminate nicely?
*/
#define EXTERN_IP_RETRY_TIMEOUT GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_MINUTES, 15)
/**
* How long do we wait until we re-try running `external-ip` if the
* command failed (but terminated)?
*/
#define EXTERN_IP_RETRY_FAILURE GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_MINUTES, 30)
/**
* How long do we wait until we re-try running `external-ip` if the
* command succeeded?
*/
#define EXTERN_IP_RETRY_SUCCESS GNUNET_TIME_relative_multiply ( \
GNUNET_TIME_UNIT_MINUTES, 5)
/**
* Handle to monitor for external IP changes.
*/
struct GN_ExternalIPMonitor
{
/**
* Kept in DLL.
*/
struct GN_ExternalIPMonitor *next;
/**
* Kept in DLL.
*/
struct GN_ExternalIPMonitor *prev;
/**
* Function to call when we believe our external IPv4 address changed.
*/
GN_NotifyExternalIPv4Change cb;
/**
* Closure for @e cb.
*/
void *cb_cls;
};
/**
* List of monitors, kept in DLL.
*/
static struct GN_ExternalIPMonitor *mon_head;
/**
* List of monitors, kept in DLL.
*/
static struct GN_ExternalIPMonitor *mon_tail;
/**
* Task run to obtain our external IP (if #enable_upnp is set
* and if we find we have a NATed IP address).
*/
static struct GNUNET_SCHEDULER_Task *probe_external_ip_task;
/**
* Handle to our operation to run `external-ip`.
*/
static struct GNUNET_NAT_ExternalHandle *probe_external_ip_op;
/**
* What is our external IP address as claimed by `external-ip`?
* 0 for unknown.
*/
static struct in_addr mini_external_ipv4;
/**
* Tell relevant clients about a change in our external
* IPv4 address.
*
* @param add #GNUNET_YES to add, #GNUNET_NO to remove
* @param v4 the external address that changed
*/
static void
notify_monitors_external_ipv4_change (int add,
const struct in_addr *v4)
{
for (struct GN_ExternalIPMonitor *mon = mon_head;
NULL != mon;
mon = mon->next)
mon->cb (mon->cb_cls,
v4,
add);
}
/**
* Task used to run `external-ip` to get our external IPv4
* address and pass it to NATed clients if possible.
*
* @param cls NULL
*/
static void
run_external_ip (void *cls);
/**
* We learn our current external IP address. If it changed,
* notify all of our applicable clients. Also re-schedule
* #run_external_ip with an appropriate timeout.
*
* @param cls NULL
* @param addr the address, NULL on errors
* @param result #GNUNET_NAT_ERROR_SUCCESS on success, otherwise the specific error code
*/
static void
handle_external_ip (void *cls,
const struct in_addr *addr,
enum GNUNET_NAT_StatusCode result)
{
char buf[INET_ADDRSTRLEN];
probe_external_ip_op = NULL;
GNUNET_SCHEDULER_cancel (probe_external_ip_task);
probe_external_ip_task
= GNUNET_SCHEDULER_add_delayed ((NULL == addr)
? EXTERN_IP_RETRY_FAILURE
: EXTERN_IP_RETRY_SUCCESS,
&run_external_ip,
NULL);
switch (result)
{
case GNUNET_NAT_ERROR_SUCCESS:
GNUNET_assert (NULL != addr);
if (addr->s_addr == mini_external_ipv4.s_addr)
return; /* not change */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Our external IP is now %s\n",
inet_ntop (AF_INET,
addr,
buf,
sizeof(buf)));
if (0 != mini_external_ipv4.s_addr)
notify_monitors_external_ipv4_change (GNUNET_NO,
&mini_external_ipv4);
mini_external_ipv4 = *addr;
notify_monitors_external_ipv4_change (GNUNET_YES,
&mini_external_ipv4);
break;
default:
if (0 != mini_external_ipv4.s_addr)
notify_monitors_external_ipv4_change (GNUNET_NO,
&mini_external_ipv4);
mini_external_ipv4.s_addr = 0;
break;
}
}
/**
* Task used to run `external-ip` to get our external IPv4
* address and pass it to NATed clients if possible.
*
* @param cls NULL
*/
static void
run_external_ip (void *cls)
{
probe_external_ip_task
= GNUNET_SCHEDULER_add_delayed (EXTERN_IP_RETRY_TIMEOUT,
&run_external_ip,
NULL);
if (NULL != probe_external_ip_op)
{
GNUNET_NAT_mini_get_external_ipv4_cancel_ (probe_external_ip_op);
probe_external_ip_op = NULL;
}
probe_external_ip_op
= GNUNET_NAT_mini_get_external_ipv4_ (&handle_external_ip,
NULL);
}
/**
* We have changed our opinion about being NATed in the first
* place. Adapt our probing.
*
* @param have_nat #GNUNET_YES if we believe we are behind NAT
*/
void
GN_nat_status_changed (int have_nat)
{
if (GNUNET_YES != enable_upnp)
return;
if ((GNUNET_YES == have_nat) &&
(NULL == probe_external_ip_task) &&
(NULL == probe_external_ip_op))
{
probe_external_ip_task
= GNUNET_SCHEDULER_add_now (&run_external_ip,
NULL);
return;
}
if (GNUNET_NO == have_nat)
{
if (NULL != probe_external_ip_task)
{
GNUNET_SCHEDULER_cancel (probe_external_ip_task);
probe_external_ip_task = NULL;
}
if (NULL != probe_external_ip_op)
{
GNUNET_NAT_mini_get_external_ipv4_cancel_ (probe_external_ip_op);
probe_external_ip_op = NULL;
}
}
}
/**
* Start monitoring external IPv4 addresses.
*
* @param cb function to call on changes
* @param cb_cls closure for @a cb
* @return handle to cancel
*/
struct GN_ExternalIPMonitor *
GN_external_ipv4_monitor_start (GN_NotifyExternalIPv4Change cb,
void *cb_cls)
{
struct GN_ExternalIPMonitor *mon;
mon = GNUNET_new (struct GN_ExternalIPMonitor);
mon->cb = cb;
mon->cb_cls = cb_cls;
GNUNET_CONTAINER_DLL_insert (mon_head,
mon_tail,
mon);
if (0 != mini_external_ipv4.s_addr)
cb (cb_cls,
&mini_external_ipv4,
GNUNET_YES);
return mon;
}
/**
* Stop calling monitor.
*
* @param mon monitor to call
*/
void
GN_external_ipv4_monitor_stop (struct GN_ExternalIPMonitor *mon)
{
GNUNET_CONTAINER_DLL_remove (mon_head,
mon_tail,
mon);
GNUNET_free (mon);
}
/* end of gnunet-service-nat_externalip.c */