/*
This file is part of GNUnet
Copyright (C) 2008--2013 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
*/
/**
* @file testbed/gnunet-daemon-testbed-blacklist.c
* @brief daemon to restrict incoming connections from other peers at the
* transport layer of a peer
* @author Sree Harsha Totakura
*/
#include "platform.h"
#include "gnunet_util_lib.h"
#include "gnunet_transport_service.h"
#include "gnunet_transport_manipulation_service.h"
#include "gnunet_ats_service.h"
#include "gnunet_testing_lib.h"
#include
/**
* Logging shorthand
*/
#define LOG(type, ...) \
GNUNET_log (type, __VA_ARGS__)
/**
* Debug logging shorthand
*/
#define DEBUG(...) \
LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
/**
* Log an error message at log-level 'level' that indicates
* a failure of the command 'cmd' on file 'filename'
* with the message given by strerror(errno).
*/
#define LOG_SQLITE(db, msg, level, cmd) \
do { \
GNUNET_log_from (level, "sqlite", _ ( \
"`%s' failed at %s:%d with error: %s\n"), \
cmd, __FILE__, __LINE__, sqlite3_errmsg (db)); \
if (msg != NULL) \
GNUNET_asprintf (msg, _ ("`%s' failed at %s:%u with error: %s"), cmd, \
__FILE__, __LINE__, sqlite3_errmsg (db)); \
} while (0)
/**
* The map to store the peer identities to allow/deny
*/
static struct GNUNET_CONTAINER_MultiPeerMap *map;
/**
* The database connection
*/
static struct sqlite3 *db;
/**
* The blacklist handle we obtain from transport when we register ourselves for
* access control
*/
static struct GNUNET_TRANSPORT_Blacklist *bh;
/**
* The hostkeys file
*/
struct GNUNET_DISK_FileHandle *hostkeys_fd;
/**
* The hostkeys map
*/
static struct GNUNET_DISK_MapHandle *hostkeys_map;
/**
* The hostkeys data
*/
static void *hostkeys_data;
/**
* Handle to the transport service. This is used for setting link metrics
*/
static struct GNUNET_TRANSPORT_ManipulationHandle *transport;
/**
* The number of hostkeys in the hostkeys array
*/
static unsigned int num_hostkeys;
/**
* @ingroup hashmap
* Iterator over hash map entries.
*
* @param cls closure
* @param key current key code
* @param value value in the hash map
* @return #GNUNET_YES if we should continue to
* iterate,
* #GNUNET_NO if not.
*/
static int
iterator (void *cls, const struct GNUNET_PeerIdentity *key, void *value)
{
GNUNET_assert (GNUNET_YES == GNUNET_CONTAINER_multipeermap_remove (map, key,
value));
return GNUNET_YES;
}
/**
* Cleaup and destroy the map
*/
static void
cleanup_map ()
{
if (NULL != map)
{
GNUNET_assert (GNUNET_SYSERR != GNUNET_CONTAINER_multipeermap_iterate (map,
&
iterator,
NULL));
GNUNET_CONTAINER_multipeermap_destroy (map);
map = NULL;
}
}
/**
* Function that decides if a connection is acceptable or not.
*
* @param cls closure
* @param pid peer to approve or disapproave
* @return GNUNET_OK if the connection is allowed, GNUNET_SYSERR if not
*/
static int
check_access (void *cls, const struct GNUNET_PeerIdentity *pid)
{
int contains;
GNUNET_assert (NULL != map);
contains = GNUNET_CONTAINER_multipeermap_contains (map, pid);
if (GNUNET_YES == contains)
{
DEBUG ("Permitting `%s'\n", GNUNET_i2s (pid));
return GNUNET_OK;
}
DEBUG ("Not permitting `%s'\n", GNUNET_i2s (pid));
return GNUNET_SYSERR;
}
static int
get_identity (unsigned int offset,
struct GNUNET_PeerIdentity *id)
{
struct GNUNET_CRYPTO_EddsaPrivateKey private_key;
if (offset >= num_hostkeys)
return GNUNET_SYSERR;
GNUNET_memcpy (&private_key,
hostkeys_data + (offset * GNUNET_TESTING_HOSTKEYFILESIZE),
GNUNET_TESTING_HOSTKEYFILESIZE);
GNUNET_CRYPTO_eddsa_key_get_public (&private_key,
&id->public_key);
return GNUNET_OK;
}
/**
* Whilelist entry
*/
struct WhiteListRow
{
/**
* Next ptr
*/
struct WhiteListRow *next;
/**
* The offset where to find the hostkey for the peer
*/
unsigned int id;
/**
* Latency to be assigned to the link
*/
int latency;
};
/**
* Function to load keys
*/
static int
load_keys (const struct GNUNET_CONFIGURATION_Handle *c)
{
char *data_dir;
char *idfile;
uint64_t fsize;
data_dir = NULL;
idfile = NULL;
fsize = 0;
data_dir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
GNUNET_asprintf (&idfile, "%s/testing_hostkeys.ecc", data_dir);
GNUNET_free (data_dir);
data_dir = NULL;
if (GNUNET_OK !=
GNUNET_DISK_file_size (idfile, &fsize, GNUNET_YES, GNUNET_YES))
{
GNUNET_free (idfile);
return GNUNET_SYSERR;
}
if (0 != (fsize % GNUNET_TESTING_HOSTKEYFILESIZE))
{
LOG (GNUNET_ERROR_TYPE_ERROR,
_ ("Incorrect hostkey file format: %s\n"), idfile);
GNUNET_free (idfile);
return GNUNET_SYSERR;
}
hostkeys_fd = GNUNET_DISK_file_open (idfile, GNUNET_DISK_OPEN_READ,
GNUNET_DISK_PERM_NONE);
if (NULL == hostkeys_fd)
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "open", idfile);
GNUNET_free (idfile);
return GNUNET_SYSERR;
}
GNUNET_free (idfile);
idfile = NULL;
hostkeys_data = GNUNET_DISK_file_map (hostkeys_fd,
&hostkeys_map,
GNUNET_DISK_MAP_TYPE_READ,
fsize);
if (NULL == hostkeys_data)
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "mmap");
return GNUNET_SYSERR;
}
num_hostkeys = fsize / GNUNET_TESTING_HOSTKEYFILESIZE;
return GNUNET_OK;
}
/**
* Function to unload keys
*/
static void
unload_keys ()
{
if (NULL != hostkeys_map)
{
GNUNET_assert (NULL != hostkeys_data);
GNUNET_DISK_file_unmap (hostkeys_map);
hostkeys_map = NULL;
hostkeys_data = NULL;
}
if (NULL != hostkeys_fd)
{
GNUNET_DISK_file_close (hostkeys_fd);
hostkeys_fd = NULL;
}
}
/**
* Shutdown task to cleanup our resources and exit.
*
* @param cls NULL
*/
static void
do_shutdown (void *cls)
{
if (NULL != transport)
{
GNUNET_TRANSPORT_manipulation_disconnect (transport);
transport = NULL;
}
cleanup_map ();
unload_keys ();
if (NULL != bh)
GNUNET_TRANSPORT_blacklist_cancel (bh);
}
/**
* Function to read whitelist rows from the database
*
* @param db the database connection
* @param pid the identity of this peer
* @param wl_rows where to store the retrieved whitelist rows
* @return GNUNET_SYSERR upon error OR the number of rows retrieved
*/
static int
db_read_whitelist (struct sqlite3 *db, int pid, struct WhiteListRow **wl_rows)
{
static const char *query_wl =
"SELECT oid, latency FROM whitelist WHERE (id == ?);";
struct sqlite3_stmt *stmt_wl;
struct WhiteListRow *lr;
int nrows;
int ret;
if (SQLITE_OK != (ret = sqlite3_prepare_v2 (db, query_wl, -1, &stmt_wl,
NULL)))
{
LOG_SQLITE (db, NULL, GNUNET_ERROR_TYPE_ERROR, "sqlite3_prepare_v2");
return GNUNET_SYSERR;
}
if (SQLITE_OK != (ret = sqlite3_bind_int (stmt_wl, 1, pid)))
{
LOG_SQLITE (db, NULL, GNUNET_ERROR_TYPE_ERROR, "sqlite3_bind_int");
sqlite3_finalize (stmt_wl);
return GNUNET_SYSERR;
}
nrows = 0;
do
{
ret = sqlite3_step (stmt_wl);
if (SQLITE_ROW != ret)
break;
nrows++;
lr = GNUNET_new (struct WhiteListRow);
lr->id = sqlite3_column_int (stmt_wl, 0);
lr->latency = sqlite3_column_int (stmt_wl, 1);
lr->next = *wl_rows;
*wl_rows = lr;
}
while (1);
sqlite3_finalize (stmt_wl);
return nrows;
}
/**
* Main function that will be run.
*
* @param cls closure
* @param args remaining command-line arguments
* @param cfgfile name of the configuration file used (for saving, can be NULL!)
* @param c configuration
*/
static void
run (void *cls, char *const *args, const char *cfgfile,
const struct GNUNET_CONFIGURATION_Handle *c)
{
char *dbfile;
struct WhiteListRow *wl_head;
struct WhiteListRow *wl_entry;
struct GNUNET_PeerIdentity identity;
struct GNUNET_ATS_Properties prop;
struct GNUNET_TIME_Relative delay;
unsigned long long pid;
unsigned int nrows;
int ret;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_number (c, "TESTBED",
"PEERID", &pid))
{
GNUNET_break (0);
return;
}
if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (c,
"TESTBED-UNDERLAY",
"DBFILE",
&dbfile))
{
GNUNET_break (0);
return;
}
if (SQLITE_OK != (ret = sqlite3_open_v2 (dbfile, &db, SQLITE_OPEN_READONLY,
NULL)))
{
if (NULL != db)
{
LOG_SQLITE (db, NULL, GNUNET_ERROR_TYPE_ERROR, "sqlite_open_v2");
GNUNET_break (SQLITE_OK == sqlite3_close (db));
}
else
LOG (GNUNET_ERROR_TYPE_ERROR, "Cannot open sqlite file %s\n", dbfile);
GNUNET_free (dbfile);
return;
}
DEBUG ("Opened database %s\n", dbfile);
GNUNET_free (dbfile);
dbfile = NULL;
wl_head = NULL;
if (GNUNET_OK != load_keys (c))
goto close_db;
transport = GNUNET_TRANSPORT_manipulation_connect (c);
if (NULL == transport)
{
GNUNET_break (0);
return;
}
/* read and process whitelist */
nrows = 0;
wl_head = NULL;
nrows = db_read_whitelist (db, pid, &wl_head);
if ((GNUNET_SYSERR == nrows) || (0 == nrows))
{
GNUNET_TRANSPORT_manipulation_disconnect (transport);
goto close_db;
}
map = GNUNET_CONTAINER_multipeermap_create (nrows, GNUNET_NO);
while (NULL != (wl_entry = wl_head))
{
wl_head = wl_entry->next;
delay.rel_value_us = wl_entry->latency;
memset (&prop, 0, sizeof(prop));
GNUNET_assert (GNUNET_OK == get_identity (wl_entry->id, &identity));
GNUNET_break (GNUNET_OK ==
GNUNET_CONTAINER_multipeermap_put (map, &identity, &identity,
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST));
DEBUG ("Setting %u ms latency to peer `%s'\n",
wl_entry->latency,
GNUNET_i2s (&identity));
GNUNET_TRANSPORT_manipulation_set (transport,
&identity,
&prop,
delay,
delay);
GNUNET_free (wl_entry);
}
bh = GNUNET_TRANSPORT_blacklist (c, &check_access, NULL);
GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL);
close_db:
GNUNET_break (SQLITE_OK == sqlite3_close (db));
}
/**
* The main function.
*
* @param argc number of arguments from the command line
* @param argv command line arguments
* @return 0 ok, 1 on error
*/
int
main (int argc, char *const *argv)
{
static const struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_OPTION_END
};
int ret;
if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
return 2;
#ifdef SQLITE_CONFIG_MMAP_SIZE
(void) sqlite3_config (SQLITE_CONFIG_MMAP_SIZE, 512000, 256000000);
#endif
ret =
(GNUNET_OK ==
GNUNET_PROGRAM_run (argc, argv, "testbed-underlay",
_
(
"Daemon to restrict underlay network in testbed deployments"),
options, &run, NULL)) ? 0 : 1;
GNUNET_free ((void*) argv);
return ret;
}