diff --git a/daemon/irc_announce.c b/daemon/irc_announce.c index 1b2255aae..4c04cffa3 100644 --- a/daemon/irc_announce.c +++ b/daemon/irc_announce.c @@ -13,12 +13,20 @@ #include #include -static bool announce_channel(const tal_t *ctx, struct ircstate *state, struct peer *p) +/* Sign a privmsg by prepending the signature to the message */ +static void sign_privmsg(struct ircstate *state, struct privmsg *msg) { - char txid[65]; int siglen; u8 der[72]; struct signature sig; + privkey_sign(state->dstate, msg->msg, strlen(msg->msg), &sig); + siglen = signature_to_der(state->dstate->secpctx, der, &sig); + msg->msg = tal_fmt(msg, "%s %s", tal_hexstr(msg, der, siglen), msg->msg); +} + +static bool announce_channel(const tal_t *ctx, struct ircstate *state, struct peer *p) +{ + char txid[65]; struct privmsg *msg = talz(ctx, struct privmsg); struct txlocator *loc = locate_tx(ctx, state->dstate, &p->anchor.txid); @@ -38,20 +46,45 @@ static bool announce_channel(const tal_t *ctx, struct ircstate *state, struct pe state->dstate->config.fee_per_satoshi, p->remote.locktime.locktime ); - - privkey_sign(state->dstate, msg->msg, strlen(msg->msg), &sig); - siglen = signature_to_der(state->dstate->secpctx, der, &sig); - msg->msg = tal_fmt(msg, "%s %s", tal_hexstr(ctx, der, siglen), msg->msg); - + sign_privmsg(state, msg); irc_send_msg(state, msg); return true; } -static void announce_channels(struct ircstate *state) +/* Send an announcement for this node to the channel, including its + * hostname, port and ID */ +static void announce_node(const tal_t *ctx, struct ircstate *state) { + char *hostname = state->dstate->external_ip; + int port = state->dstate->portnum; + struct privmsg *msg = talz(ctx, struct privmsg); + + if (hostname == NULL) { + //FIXME: log that we don't know our IP yet. + return; + } + + msg->channel = "#lightning-nodes"; + msg->msg = tal_fmt( + msg, "NODE %s %s %d", + pubkey_to_hexstr(msg, state->dstate->secpctx, &state->dstate->id), + hostname, + port + ); + + sign_privmsg(state, msg); + irc_send_msg(state, msg); +} + +/* Announce the node's contact information and all of its channels */ +static void announce(struct ircstate *state) +{ + tal_t *ctx = tal(state, tal_t); struct peer *p; + announce_node(ctx, state); + list_for_each(&state->dstate->peers, p, list) { if (!state_is_normal(p->state)) @@ -60,7 +93,7 @@ static void announce_channels(struct ircstate *state) } tal_free(ctx); - new_reltimer(state->dstate, state, time_from_sec(60), announce_channels, state); + new_reltimer(state->dstate, state, time_from_sec(60), announce, state); } /* Reconnect to IRC server upon disconnection. */ @@ -69,42 +102,41 @@ static void handle_irc_disconnect(struct ircstate *state) new_reltimer(state->dstate, state, state->reconnect_timeout, irc_connect, state); } -/* - * Handle an incoming message by checking if it is a channel - * announcement, parse it and add the channel to the topology if yes. - * - * The format for a valid announcement is: - * CHAN - * - */ -static void handle_irc_privmsg(struct ircstate *istate, const struct privmsg *msg) +/* Verify a signed privmsg */ +static bool verify_signed_privmsg( + struct ircstate *istate, + const struct pubkey *pk, + const struct privmsg *msg) { - int blkheight; - char **splits = tal_strsplit(msg, msg->msg + 1, " ", STR_NO_EMPTY); - - if (tal_count(splits) != 11 || !streq(splits[1], "CHAN")) - return; - - int siglen = hex_data_size(strlen(splits[0])); - u8 *der = tal_hexdata(msg, splits[0], strlen(splits[0])); - if (der == NULL) - return; - struct signature sig; struct sha256_double hash; - char *content = strchr(msg->msg, ' ') + 1; + const char *m = msg->msg + 1; + int siglen = strchr(m, ' ') - m; + const char *content = m + siglen + 1; + u8 *der = tal_hexdata(msg, m, siglen); + + siglen = hex_data_size(siglen); + if (der == NULL) + return false; + if (!signature_from_der(istate->dstate->secpctx, der, siglen, &sig)) - return; - + return false; sha256_double(&hash, content, strlen(content)); - splits++; + return check_signed_hash(istate->dstate->secpctx, &hash, &sig, pk); +} +static void handle_channel_announcement( + struct ircstate *istate, + const struct privmsg *msg, + char **splits) +{ struct pubkey *pk1 = talz(msg, struct pubkey); struct pubkey *pk2 = talz(msg, struct pubkey); struct sha256_double *txid = talz(msg, struct sha256_double); int index; - bool ok = true; + int blkheight; + ok &= pubkey_from_hexstr(istate->dstate->secpctx, splits[1], strlen(splits[1]), pk1); ok &= pubkey_from_hexstr(istate->dstate->secpctx, splits[2], strlen(splits[2]), pk2); ok &= bitcoin_txid_from_hex(splits[3], strlen(splits[3]), txid); @@ -115,7 +147,7 @@ static void handle_irc_privmsg(struct ircstate *istate, const struct privmsg *ms return; } - if (!check_signed_hash(istate->dstate->secpctx, &hash, &sig, pk1)) { + if (!verify_signed_privmsg(istate, pk1, msg)) { log_debug(istate->log, "Ignoring announcement from %s, signature check failed.", splits[1]); @@ -131,11 +163,80 @@ static void handle_irc_privmsg(struct ircstate *istate, const struct privmsg *ms atoi(splits[7]), atoi(splits[8]), 6); } +static void handle_node_announcement( + struct ircstate *istate, + const struct privmsg *msg, + char **splits) +{ + struct pubkey *pk = talz(msg, struct pubkey); + char *hostname = tal_strdup(msg, splits[2]); + int port = atoi(splits[3]); + + if (!pubkey_from_hexstr(istate->dstate->secpctx, splits[1], strlen(splits[1]), pk) || port < 1) + return; + + if (!verify_signed_privmsg(istate, pk, msg)) { + log_debug(istate->log, "Ignoring node announcement from %s, signature check failed.", + splits[1]); + return; + } + + add_node(istate->dstate, pk, hostname, port); +} + +/* + * Handle an incoming message by checking if it is a channel + * announcement, parse it and add the channel to the topology if yes. + * + * The format for a valid announcement is: + * CHAN + * + */ +static void handle_irc_privmsg(struct ircstate *istate, const struct privmsg *msg) +{ + char **splits = tal_strsplit(msg, msg->msg + 1, " ", STR_NO_EMPTY); + int splitcount = tal_count(splits) - 1; + + if (splitcount < 2) + return; + + char *type = splits[1]; + + if (splitcount == 10 && streq(type, "CHAN")) + handle_channel_announcement(istate, msg, splits + 1); + else if (splitcount == 5 && streq(type, "NODE")) + handle_node_announcement(istate, msg, splits + 1); +} + +static void handle_irc_command(struct ircstate *istate, const struct irccommand *cmd) +{ + struct lightningd_state *dstate = istate->dstate; + char **params = tal_strsplit(cmd, cmd->params, " ", STR_NO_EMPTY); + int numparams = tal_count(params) - 1; + + if (streq(cmd->command, "378")) { + dstate->external_ip = tal_strdup( + istate->dstate, params[numparams - 1]); + + // Add our node to the node_map for completeness + add_node(istate->dstate, &dstate->id, + dstate->external_ip, dstate->portnum); + } +} + +static void handle_irc_connected(struct ircstate *istate) +{ + irc_send(istate, "JOIN", "#lightning-nodes"); + irc_send(istate, "WHOIS", "%s", istate->nick); +} + void setup_irc_connection(struct lightningd_state *dstate) { // Register callback irc_privmsg_cb = *handle_irc_privmsg; + irc_connect_cb = *handle_irc_connected; irc_disconnect_cb = *handle_irc_disconnect; + irc_command_cb = *handle_irc_command; struct ircstate *state = talz(dstate, struct ircstate); state->dstate = dstate; @@ -151,5 +252,5 @@ void setup_irc_connection(struct lightningd_state *dstate) pubkey_to_hexstr(state, dstate->secpctx, &dstate->id) + 1); irc_connect(state); - announce_channels(state); + announce(state); } diff --git a/daemon/jsonrpc.c b/daemon/jsonrpc.c index 1330c2519..946b7d78e 100644 --- a/daemon/jsonrpc.c +++ b/daemon/jsonrpc.c @@ -287,6 +287,7 @@ static const struct json_command *cmdlist[] = { &getlog_command, &connect_command, &getpeers_command, + &getnodes_command, &gethtlcs_command, &close_command, &newaddr_command, diff --git a/daemon/jsonrpc.h b/daemon/jsonrpc.h index 20d9cef6e..274f0d4b3 100644 --- a/daemon/jsonrpc.h +++ b/daemon/jsonrpc.h @@ -62,6 +62,7 @@ extern const struct json_command connect_command; extern const struct json_command close_command; extern const struct json_command getchannels_command; extern const struct json_command getpeers_command; +extern const struct json_command getnodes_command; /* Invoice management. */ extern const struct json_command invoice_command; diff --git a/daemon/lightningd.h b/daemon/lightningd.h index 20f27b39c..4123b28a3 100644 --- a/daemon/lightningd.h +++ b/daemon/lightningd.h @@ -133,5 +133,8 @@ struct lightningd_state { /* Re-exec hack for testing. */ char **reexec; + + /* IP/hostname to be announced for incoming connections */ + char *external_ip; }; #endif /* LIGHTNING_DAEMON_LIGHTNING_H */ diff --git a/daemon/routing.c b/daemon/routing.c index e2fddf5b0..2d27fa1d6 100644 --- a/daemon/routing.c +++ b/daemon/routing.c @@ -28,6 +28,7 @@ static bool node_eq(const struct node *n, const secp256k1_pubkey *key) { return structeq(&n->id.pubkey, key); } + HTABLE_DEFINE_TYPE(struct node, keyof_node, hash_key, node_eq, node_map); struct node_map *empty_node_map(struct lightningd_state *dstate) @@ -63,12 +64,33 @@ struct node *new_node(struct lightningd_state *dstate, n->id = *id; n->in = tal_arr(n, struct node_connection *, 0); n->out = tal_arr(n, struct node_connection *, 0); + n->port = 0; node_map_add(dstate->nodes, n); tal_add_destructor(n, destroy_node); return n; } +struct node *add_node( + struct lightningd_state *dstate, + const struct pubkey *pk, + char *hostname, + int port) +{ + struct node *n = get_node(dstate, pk); + if (!n) { + n = new_node(dstate, pk); + log_debug_struct(dstate->base_log, "Creating new node %s", + struct pubkey, pk); + } else { + log_debug_struct(dstate->base_log, "Update existing node %s", + struct pubkey, pk); + } + n->hostname = tal_steal(n, hostname); + n->port = port; + return n; +} + static bool remove_conn_from_array(struct node_connection ***conns, struct node_connection *nc) { @@ -526,4 +548,40 @@ const struct json_command dev_routefail_command = { "Returns an empty result on success" }; +static void json_getnodes(struct command *cmd, + const char *buffer, const jsmntok_t *params) +{ + struct json_result *response = new_json_result(cmd); + struct node *n; + struct node_map_iter i; + n = node_map_first(cmd->dstate->nodes, &i); + + json_object_start(response, NULL); + json_array_start(response, "nodes"); + + while (n != NULL) { + json_object_start(response, NULL); + json_add_pubkey(response, cmd->dstate->secpctx, + "nodeid", &n->id); + json_add_num(response, "port", n->port); + if (!n->port) + json_add_null(response, "hostname"); + else + json_add_string(response, "hostname", n->hostname); + + json_object_end(response); + n = node_map_next(cmd->dstate->nodes, &i); + } + + json_array_end(response); + json_object_end(response); + command_success(cmd, response); +} + +const struct json_command getnodes_command = { + "getnodes", + json_getnodes, + "List all known nodes in the network.", + "Returns a 'nodes' array" +}; diff --git a/daemon/routing.h b/daemon/routing.h index faeede061..5430c47ba 100644 --- a/daemon/routing.h +++ b/daemon/routing.h @@ -20,6 +20,11 @@ struct node_connection { struct node { struct pubkey id; + + /* IP/Hostname and port of this node */ + char *hostname; + int port; + /* Routes connecting to us, from us. */ struct node_connection **in, **out; @@ -46,6 +51,12 @@ struct node *get_node(struct lightningd_state *dstate, * If it returns more than msatoshi, it overflowed. */ s64 connection_fee(const struct node_connection *c, u64 msatoshi); +/* Updates existing node, or creates a new one as required. */ +struct node *add_node(struct lightningd_state *dstate, + const struct pubkey *pk, + char *hostname, + int port); + /* Updates existing connection, or creates new one as required. */ struct node_connection *add_connection(struct lightningd_state *dstate, const struct pubkey *from, diff --git a/irc.c b/irc.c index f65eb371f..756553a7b 100644 --- a/irc.c +++ b/irc.c @@ -3,6 +3,8 @@ #include "irc.h" void (*irc_privmsg_cb)(struct ircstate *, const struct privmsg *) = NULL; +void (*irc_command_cb)(struct ircstate *, const struct irccommand *) = NULL; +void (*irc_connect_cb)(struct ircstate *) = NULL; void (*irc_disconnect_cb)(struct ircstate *) = NULL; static struct io_plan *irc_connected(struct io_conn *conn, struct lightningd_state *dstate, struct ircstate *state); @@ -63,7 +65,7 @@ static struct io_plan *irc_write_loop(struct io_conn *conn, struct ircstate *sta ); } -/* +/* * Called by the read loop to handle individual lines. This splits the * line into a struct irccommand and passes it on to the specific * handlers for the irccommand type. It silently drops any irccommand @@ -94,10 +96,14 @@ static void handle_irc_command(struct ircstate *state, const char *line) pm->msg = tal_strjoin(m, splits + 2, " ", STR_NO_TRAIL); irc_privmsg_cb(state, pm); } + + if (irc_command_cb != NULL) + irc_command_cb(state, m); + tal_free(m); } -/* +/* * Read incoming data and split it along the newline boundaries. Takes * care of buffering incomplete lines and passes the lines to the * handle_irc_command handler. @@ -168,7 +174,9 @@ static struct io_plan *irc_connected(struct io_conn *conn, struct lightningd_sta state->connected = true; irc_send(state, "USER", "%s 0 * :A lightning node", state->nick); irc_send(state, "NICK", "%s", state->nick); - irc_send(state, "JOIN", "#lightning-nodes"); + + if (irc_connect_cb != NULL) + irc_connect_cb(state); return io_duplex(conn, io_read_partial(conn, diff --git a/irc.h b/irc.h index 068886c37..facacfd9e 100644 --- a/irc.h +++ b/irc.h @@ -53,8 +53,10 @@ struct ircstate { struct timerel reconnect_timeout; }; -/* Callback to register for incoming messages */ +/* Callbacks to register for incoming messages, events and raw commands */ extern void (*irc_privmsg_cb)(struct ircstate *, const struct privmsg *); +extern void (*irc_command_cb)(struct ircstate *, const struct irccommand *); +extern void (*irc_connect_cb)(struct ircstate *); extern void (*irc_disconnect_cb)(struct ircstate *); /* Send messages to IRC */