/* This file is part of GNUnet. Copyright (C) 2012, 2017 GNUnet e.V. GNUnet 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 3, 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 General Public License for more details. You should have received a copy of the GNU General Public License along with GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * @file cadet/gnunet-cadet.c * @brief Print information about cadet tunnels and peers. * @author Bartlomiej Polot * @author Christian Grothoff */ #include "platform.h" #include "gnunet_util_lib.h" #include "gnunet_cadet_service.h" #include "cadet.h" /** * Option -P. */ static int request_peers; /** * Option --peer */ static char *peer_id; /** * Option -T. */ static int request_tunnels; /** * Option --tunnel */ static char *tunnel_id; /** * Option --connection */ static char *conn_id; /** * Option --channel */ static char *channel_id; /** * Port to listen on (-o). */ static char *listen_port; /** * Request echo service */ static int echo; /** * Request a debug dump */ static int dump; /** * Time of last echo request. */ static struct GNUNET_TIME_Absolute echo_time; /** * Task for next echo request. */ static struct GNUNET_SCHEDULER_Task *echo_task; /** * Peer to connect to. */ static char *target_id; /** * Port to connect to */ static char *target_port = "default"; /** * Cadet handle. */ static struct GNUNET_CADET_Handle *mh; /** * Channel handle. */ static struct GNUNET_CADET_Channel *ch; /** * HashCode of the given port string */ static struct GNUNET_HashCode porthash; /** * Data structure for ongoing reception of incoming virtual circuits. */ struct GNUNET_CADET_Port *lp; /** * Task for reading from stdin. */ static struct GNUNET_SCHEDULER_Task *rd_task; /** * Task for main job. */ static struct GNUNET_SCHEDULER_Task *job; /** * Wait for input on STDIO and send it out over the #ch. */ static void listen_stdio (void); /** * Convert encryption status to human readable string. * * @param status Encryption status. * * @return Human readable string. */ static const char * enc_2s (uint16_t status) { switch (status) { case 0: return "NULL "; case 1: return "KSENT"; case 2: return "KRECV"; case 3: return "READY"; default: return ""; } } /** * Convert connection status to human readable string. * * @param status Connection status. * * @return Human readable string. */ static const char * conn_2s (uint16_t status) { switch (status) { case 0: return "NEW "; case 1: return "SRCH "; case 2: return "WAIT "; case 3: return "READY"; case 4: return "SHUTD"; default: return ""; } } /** * Task to shut down this application. * * @param cls Closure (unused). */ static void shutdown_task (void *cls) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Shutdown\n"); if (NULL != ch) { GNUNET_CADET_channel_destroy (ch); ch = NULL; } if (NULL != mh) { GNUNET_CADET_disconnect (mh); mh = NULL; } if (NULL != rd_task) { GNUNET_SCHEDULER_cancel (rd_task); rd_task = NULL; } if (NULL != echo_task) { GNUNET_SCHEDULER_cancel (echo_task); echo_task = NULL; } if (NULL != job) { GNUNET_SCHEDULER_cancel (job); job = NULL; } } /** * Task run in stdio mode, after some data is available at stdin. * * @param cls Closure (unused). */ static void read_stdio (void *cls) { struct GNUNET_MQ_Envelope *env; struct GNUNET_MessageHeader *msg; char buf[60000]; ssize_t data_size; rd_task = NULL; data_size = read (0, buf, 60000); if (data_size < 1) { GNUNET_SCHEDULER_shutdown(); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Read %u bytes from stdio\n", (unsigned int) data_size); env = GNUNET_MQ_msg_extra (msg, data_size, GNUNET_MESSAGE_TYPE_CADET_CLI); GNUNET_memcpy (&msg[1], buf, data_size); GNUNET_MQ_send (GNUNET_CADET_get_mq (ch), env); if (GNUNET_NO == echo) { listen_stdio (); } else { echo_time = GNUNET_TIME_absolute_get (); } } /** * Wait for input on STDIO and send it out over the #ch. */ static void listen_stdio () { struct GNUNET_NETWORK_FDSet *rs; /* FIXME: why use 'rs' here, seems overly complicated... */ rs = GNUNET_NETWORK_fdset_create (); GNUNET_NETWORK_fdset_set_native (rs, 0); /* STDIN */ rd_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, GNUNET_TIME_UNIT_FOREVER_REL, rs, NULL, &read_stdio, NULL); GNUNET_NETWORK_fdset_destroy (rs); } /** * Function called whenever a channel is destroyed. Should clean up * any associated state. * * It must NOT call #GNUNET_CADET_channel_destroy on the channel. * * @param cls closure * @param channel connection to the other end (henceforth invalid) */ static void channel_ended (void *cls, const struct GNUNET_CADET_Channel *channel) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Channel ended!\n"); GNUNET_assert (channel == ch); ch = NULL; GNUNET_SCHEDULER_shutdown (); } /** * Method called whenever another peer has added us to a channel * the other peer initiated. * Only called (once) upon reception of data with a message type which was * subscribed to in #GNUNET_CADET_connect. * * A call to #GNUNET_CADET_channel_destroy causes the channel to be ignored. * In this case the handler MUST return NULL. * * @param cls closure * @param channel new handle to the channel * @param initiator peer that started the channel * @return initial channel context for the channel, we use @a channel */ static void * channel_incoming (void *cls, struct GNUNET_CADET_Channel *channel, const struct GNUNET_PeerIdentity *initiator) { GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "Incoming connection from %s\n", GNUNET_i2s_full (initiator)); GNUNET_assert (NULL == ch); GNUNET_assert (NULL != lp); GNUNET_CADET_close_port (lp); lp = NULL; ch = channel; if (GNUNET_NO == echo) listen_stdio (); return channel; } /** * @brief Send an echo request to the remote peer. * * @param cls Closure (NULL). */ static void send_echo (void *cls) { struct GNUNET_MQ_Envelope *env; struct GNUNET_MessageHeader *msg; echo_task = NULL; if (NULL == ch) return; env = GNUNET_MQ_msg (msg, GNUNET_MESSAGE_TYPE_CADET_CLI); GNUNET_MQ_send (GNUNET_CADET_get_mq (ch), env); } /** * Call CADET's monitor API, request debug dump on the service. * * @param cls Closure (unused). */ static void request_dump (void *cls) { GNUNET_CADET_request_dump (mh); GNUNET_SCHEDULER_shutdown (); } /** * Check data message sanity. Does nothing so far (all messages are OK). * * @param cls Closure (unused). * @param message The message to check. * @return #GNUNET_OK to keep the channel open, * #GNUNET_SYSERR to close it (signal serious error). */ static int check_data (void *cls, const struct GNUNET_MessageHeader *message) { return GNUNET_OK; /* all is well-formed */ } /** * Function called whenever a message is received. * * Each time the function must call #GNUNET_CADET_receive_done on the channel * in order to receive the next message. This doesn't need to be immediate: * can be delayed if some processing is done on the message. * * @param cls NULL * @param message The actual message. */ static void handle_data (void *cls, const struct GNUNET_MessageHeader *message) { size_t payload_size = ntohs (message->size) - sizeof (*message); uint16_t len; ssize_t done; uint16_t off; const char *buf; GNUNET_CADET_receive_done (ch); if (GNUNET_YES == echo) { if (NULL != listen_port) { struct GNUNET_MQ_Envelope *env; struct GNUNET_MessageHeader *msg; env = GNUNET_MQ_msg_extra (msg, payload_size, GNUNET_MESSAGE_TYPE_CADET_CLI); GNUNET_memcpy (&msg[1], &message[1], payload_size); GNUNET_MQ_send (GNUNET_CADET_get_mq (ch), env); return; } else { struct GNUNET_TIME_Relative latency; latency = GNUNET_TIME_absolute_get_duration (echo_time); echo_time = GNUNET_TIME_UNIT_FOREVER_ABS; GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "time: %s\n", GNUNET_STRINGS_relative_time_to_string (latency, GNUNET_NO)); echo_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, &send_echo, NULL); } } len = ntohs (message->size) - sizeof (*message); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got %u bytes\n", len); buf = (const char *) &message[1]; off = 0; while (off < len) { done = write (1, &buf[off], len - off); if (done <= 0) { if (-1 == done) GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "write"); GNUNET_SCHEDULER_shutdown (); return; } off += done; } } /** * Method called to retrieve information about all peers in CADET, called * once per peer. * * After last peer has been reported, an additional call with NULL is done. * * @param cls Closure. * @param peer Peer, or NULL on "EOF". * @param tunnel Do we have a tunnel towards this peer? * @param n_paths Number of known paths towards this peer. * @param best_path How long is the best path? * (0 = unknown, 1 = ourselves, 2 = neighbor) */ static void peers_callback (void *cls, const struct GNUNET_PeerIdentity *peer, int tunnel, unsigned int n_paths, unsigned int best_path) { if (NULL == peer) { GNUNET_SCHEDULER_shutdown(); return; } FPRINTF (stdout, "%s tunnel: %c, paths: %u\n", GNUNET_i2s_full (peer), tunnel ? 'Y' : 'N', n_paths); } /** * Method called to retrieve information about a specific peer * known to the service. * * @param cls Closure. * @param peer Peer ID. * @param tunnel Do we have a tunnel towards this peer? #GNUNET_YES/#GNUNET_NO * @param neighbor Is this a direct neighbor? #GNUNET_YES/#GNUNET_NO * @param n_paths Number of paths known towards peer. * @param paths Array of PEER_IDs representing all paths to reach the peer. * Each path starts with the local peer. * Each path ends with the destination peer (given in @c peer). */ static void peer_callback (void *cls, const struct GNUNET_PeerIdentity *peer, int tunnel, int neighbor, unsigned int n_paths, const struct GNUNET_PeerIdentity *paths) { unsigned int i; const struct GNUNET_PeerIdentity *p; FPRINTF (stdout, "%s [TUNNEL: %s, NEIGHBOR: %s, PATHS: %u]\n", GNUNET_i2s_full (peer), tunnel ? "Y" : "N", neighbor ? "Y" : "N", n_paths); p = paths; for (i = 0; i < n_paths && NULL != p;) { FPRINTF (stdout, "%s ", GNUNET_i2s (p)); if (0 == memcmp (p, peer, sizeof (*p))) { FPRINTF (stdout, "\n"); i++; } p++; } GNUNET_SCHEDULER_shutdown(); } /** * Method called to retrieve information about all tunnels in CADET. * * @param cls Closure. * @param peer Destination peer. * @param channels Number of channels. * @param connections Number of connections. * @param estate Encryption state. * @param cstate Connectivity state. */ static void tunnels_callback (void *cls, const struct GNUNET_PeerIdentity *peer, unsigned int channels, unsigned int connections, uint16_t estate, uint16_t cstate) { if (NULL == peer) { GNUNET_SCHEDULER_shutdown(); return; } FPRINTF (stdout, "%s [ENC: %s, CON: %s] CHs: %u, CONNs: %u\n", GNUNET_i2s_full (peer), enc_2s (estate), conn_2s (cstate), channels, connections); } /** * Method called to retrieve information about a specific tunnel the cadet peer * has established, o`r is trying to establish. * * @param cls Closure. * @param peer Peer towards whom the tunnel is directed. * @param n_channels Number of channels. * @param n_connections Number of connections. * @param channels Channels. * @param connections Connections. * @param estate Encryption status. * @param cstate Connectivity status. */ static void tunnel_callback (void *cls, const struct GNUNET_PeerIdentity *peer, unsigned int n_channels, unsigned int n_connections, const struct GNUNET_CADET_ChannelTunnelNumber *channels, const struct GNUNET_CADET_ConnectionTunnelIdentifier *connections, unsigned int estate, unsigned int cstate) { unsigned int i; if (NULL != peer) { FPRINTF (stdout, "Tunnel %s\n", GNUNET_i2s_full (peer)); FPRINTF (stdout, "\t%u channels\n", n_channels); for (i = 0; i < n_channels; i++) FPRINTF (stdout, "\t\t%X\n", ntohl (channels[i].cn)); FPRINTF (stdout, "\t%u connections\n", n_connections); for (i = 0; i < n_connections; i++) FPRINTF (stdout, "\t\t%s\n", GNUNET_sh2s (&connections[i].connection_of_tunnel)); FPRINTF (stdout, "\tencryption state: %s\n", enc_2s (estate)); FPRINTF (stdout, "\tconnection state: %s\n", conn_2s (cstate)); } GNUNET_SCHEDULER_shutdown (); } /** * Call CADET's meta API, get all peers known to a peer. * * @param cls Closure (unused). */ static void get_peers (void *cls) { job = NULL; GNUNET_CADET_get_peers (mh, &peers_callback, NULL); } /** * Call CADET's monitor API, get info of one peer. * * @param cls Closure (unused). */ static void show_peer (void *cls) { struct GNUNET_PeerIdentity pid; job = NULL; if (GNUNET_OK != GNUNET_CRYPTO_eddsa_public_key_from_string (peer_id, strlen (peer_id), &pid.public_key)) { fprintf (stderr, _("Invalid peer ID `%s'\n"), peer_id); GNUNET_SCHEDULER_shutdown(); return; } GNUNET_CADET_get_peer (mh, &pid, peer_callback, NULL); } /** * Call CADET's meta API, get all tunnels known to a peer. * * @param cls Closure (unused). */ static void get_tunnels (void *cls) { job = NULL; GNUNET_CADET_get_tunnels (mh, &tunnels_callback, NULL); } /** * Call CADET's monitor API, get info of one tunnel. * * @param cls Closure (unused). */ static void show_tunnel (void *cls) { struct GNUNET_PeerIdentity pid; if (GNUNET_OK != GNUNET_CRYPTO_eddsa_public_key_from_string (tunnel_id, strlen (tunnel_id), &pid.public_key)) { fprintf (stderr, _("Invalid tunnel owner `%s'\n"), tunnel_id); GNUNET_SCHEDULER_shutdown (); return; } GNUNET_CADET_get_tunnel (mh, &pid, &tunnel_callback, NULL); } /** * Call CADET's monitor API, get info of one channel. * * @param cls Closure (unused). */ static void show_channel (void *cls) { job = NULL; GNUNET_break (0); } /** * Call CADET's monitor API, get info of one connection. * * @param cls Closure (unused). */ static void show_connection (void *cls) { job = NULL; GNUNET_break (0); } /** * Main function that will be run by the scheduler. * * @param cls closure * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be NULL!) * @param cfg configuration */ static void run (void *cls, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *cfg) { struct GNUNET_MQ_MessageHandler handlers[] = { GNUNET_MQ_hd_var_size (data, GNUNET_MESSAGE_TYPE_CADET_CLI, struct GNUNET_MessageHeader, NULL), GNUNET_MQ_handler_end () }; /* FIXME add option to monitor apps */ target_id = args[0]; if (target_id && args[1]) target_port = args[1]; if ( (0 != (request_peers | request_tunnels) || NULL != tunnel_id || NULL != conn_id || NULL != channel_id) && target_id != NULL) { FPRINTF (stderr, _("Extra arguments are not applicable " "in combination with this option.\n")); return; } if (GNUNET_YES == dump) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Requesting debug dump\n"); job = GNUNET_SCHEDULER_add_now (&request_dump, NULL); } else if (NULL != peer_id) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Show peer\n"); job = GNUNET_SCHEDULER_add_now (&show_peer, NULL); } else if (NULL != tunnel_id) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Show tunnel\n"); job = GNUNET_SCHEDULER_add_now (&show_tunnel, NULL); } else if (NULL != channel_id) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Show channel\n"); job = GNUNET_SCHEDULER_add_now (&show_channel, NULL); } else if (NULL != conn_id) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Show connection\n"); job = GNUNET_SCHEDULER_add_now (&show_connection, NULL); } else if (GNUNET_YES == request_peers) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Show all peers\n"); job = GNUNET_SCHEDULER_add_now (&get_peers, NULL); } else if (GNUNET_YES == request_tunnels) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Show all tunnels\n"); job = GNUNET_SCHEDULER_add_now (&get_tunnels, NULL); } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting to CADET service\n"); mh = GNUNET_CADET_connect (cfg); GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL); if (NULL == mh) { GNUNET_SCHEDULER_shutdown (); return; } if (NULL != listen_port) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Opening CADET listen port\n"); GNUNET_CRYPTO_hash (listen_port, strlen (listen_port), &porthash); lp = GNUNET_CADET_open_port (mh, &porthash, &channel_incoming, NULL, NULL /* window changes */, &channel_ended, handlers); } if (NULL != target_id) { struct GNUNET_PeerIdentity pid; enum GNUNET_CADET_ChannelOption opt; if (GNUNET_OK != GNUNET_CRYPTO_eddsa_public_key_from_string (target_id, strlen (target_id), &pid.public_key)) { GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, _("Invalid target `%s'\n"), target_id); GNUNET_SCHEDULER_shutdown (); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Connecting to `%s:%s'\n", target_id, target_port); opt = GNUNET_CADET_OPTION_DEFAULT | GNUNET_CADET_OPTION_RELIABLE; GNUNET_CRYPTO_hash (target_port, strlen(target_port), &porthash); ch = GNUNET_CADET_channel_create (mh, NULL, &pid, &porthash, opt, NULL /* window changes */, &channel_ended, handlers); if (GNUNET_YES == echo) { echo_task = GNUNET_SCHEDULER_add_now (&send_echo, NULL); } else { listen_stdio (); } } if ( (NULL == lp) && (NULL == job) && (NULL == ch) ) { GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, _("No action requested\n")); GNUNET_SCHEDULER_shutdown (); return; } } /** * The main function to obtain peer information. * * @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) { int res; const char helpstr[] = "Create tunnels and retrieve info about CADET's status."; struct GNUNET_GETOPT_CommandLineOption options[] = { /* I would use the terminology 'circuit' here... --lynX */ GNUNET_GETOPT_option_string ('C', "connection", "CONNECTION_ID", gettext_noop ("Provide information about a particular connection"), &conn_id), GNUNET_GETOPT_option_flag ('e', "echo", gettext_noop ("Activate echo mode"), &echo), GNUNET_GETOPT_option_flag ('d', "dump", gettext_noop ("Dump debug information to STDERR"), &dump), GNUNET_GETOPT_option_string ('o', "open-port", "SHARED_SECRET", gettext_noop ("Listen for connections using a shared secret among sender and recipient"), &listen_port), GNUNET_GETOPT_option_string ('p', "peer", "PEER_ID", gettext_noop ("Provide information about a patricular peer"), &peer_id), GNUNET_GETOPT_option_flag ('P', "peers", gettext_noop ("Provide information about all peers"), &request_peers), GNUNET_GETOPT_option_string ('t', "tunnel", "TUNNEL_ID", gettext_noop ("Provide information about a particular tunnel"), &tunnel_id), GNUNET_GETOPT_option_flag ('T', "tunnels", gettext_noop ("Provide information about all tunnels"), &request_tunnels), GNUNET_GETOPT_OPTION_END }; if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv)) return 2; res = GNUNET_PROGRAM_run (argc, argv, "gnunet-cadet (OPTIONS | PEER_ID SHARED_SECRET)", gettext_noop (helpstr), options, &run, NULL); GNUNET_free ((void *) argv); if (GNUNET_OK == res) return 0; return 1; } /* end of gnunet-cadet.c */