/* 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; }