core-lightning/daemon/irc_announce.c
Christian Decker 2a5a114f27 irc: Handle node announcements
lightningd now uses a WHOIS query on itself to learn its external IP
address and announces that on the channel with the NODE message. It also
tracks other nodes in the routing table.

Refactored the signature verification to reuse it for both CHAN and NODE
messages.
2016-10-02 14:52:14 +02:00

257 lines
7.1 KiB
C

#include "bitcoin/privkey.h"
#include "bitcoin/signature.h"
#include "daemon/chaintopology.h"
#include "daemon/irc_announce.h"
#include "daemon/lightningd.h"
#include "daemon/log.h"
#include "daemon/peer.h"
#include "daemon/routing.h"
#include "daemon/secrets.h"
#include "daemon/timeout.h"
#include "utils.h"
#include <ccan/list/list.h>
#include <ccan/str/hex/hex.h>
/* Sign a privmsg by prepending the signature to the message */
static void sign_privmsg(struct ircstate *state, struct privmsg *msg)
{
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);
if (loc == NULL)
return false;
bitcoin_txid_to_hex(&p->anchor.txid, txid, sizeof(txid));
msg->channel = "#lightning-nodes";
msg->msg = tal_fmt(
msg, "CHAN %s %s %s %d %d %d %d %d",
pubkey_to_hexstr(msg, state->dstate->secpctx, &state->dstate->id),
pubkey_to_hexstr(msg, state->dstate->secpctx, p->id),
txid,
loc->blkheight,
loc->index,
state->dstate->config.fee_base,
state->dstate->config.fee_per_satoshi,
p->remote.locktime.locktime
);
sign_privmsg(state, msg);
irc_send_msg(state, msg);
return true;
}
/* 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))
continue;
announce_channel(ctx, state, p);
}
tal_free(ctx);
new_reltimer(state->dstate, state, time_from_sec(60), announce, state);
}
/* Reconnect to IRC server upon disconnection. */
static void handle_irc_disconnect(struct ircstate *state)
{
new_reltimer(state->dstate, state, state->reconnect_timeout, irc_connect, state);
}
/* Verify a signed privmsg */
static bool verify_signed_privmsg(
struct ircstate *istate,
const struct pubkey *pk,
const struct privmsg *msg)
{
struct signature sig;
struct sha256_double hash;
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 false;
sha256_double(&hash, content, strlen(content));
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);
blkheight = atoi(splits[4]);
index = atoi(splits[5]);
if (!ok || index < 0 || blkheight < 0) {
log_debug(istate->dstate->base_log, "Unable to parse channel announcent.");
return;
}
if (!verify_signed_privmsg(istate, pk1, msg)) {
log_debug(istate->log,
"Ignoring announcement from %s, signature check failed.",
splits[1]);
return;
}
/*
* FIXME Check in topology that the tx is in the block and
* that the endpoints match.
*/
add_connection(istate->dstate, pk1, pk2, atoi(splits[6]),
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:
* <sig> CHAN <pk1> <pk2> <anchor txid> <block height> <tx position> <base_fee>
* <proportional_fee> <locktime>
*/
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;
state->server = "irc.freenode.net";
state->reconnect_timeout = time_from_sec(15);
state->log = new_log(state, state->dstate->log_record, "%s:irc",
log_prefix(state->dstate->base_log));
/* Truncate nick at 13 bytes, would be imposed by freenode anyway */
state->nick = tal_fmt(
state,
"N%.12s",
pubkey_to_hexstr(state, dstate->secpctx, &dstate->id) + 1);
irc_connect(state);
announce(state);
}