From 96839290fb34316425ef8d625fefa8e1a58dabcb Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 7 Sep 2016 23:29:49 +0200 Subject: [PATCH 1/3] routing: Added simple IRC library based on io_loop The IRC library can login and keep its connection alive by replying to PING messages. It also exposes a callback that handles PRIVMSG commands and can inject messages from outside, e.g., based on a timer or a lightning event. --- irc.c | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ irc.h | 67 ++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 irc.c create mode 100644 irc.h diff --git a/irc.c b/irc.c new file mode 100644 index 000000000000..d1eebac1cf75 --- /dev/null +++ b/irc.c @@ -0,0 +1,178 @@ +#include "irc.h" +#include "daemon/dns.h" +#include "daemon/log.h" + +void (*irc_privmsg_cb)(struct ircstate *, const struct privmsg *) = 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); +static void irc_disconnected(struct io_conn *conn, struct ircstate *state); + +bool irc_send_msg(struct ircstate *state, struct privmsg *m) +{ + return irc_send(state, "PRIVMSG", "%s :%s", m->channel, m->msg); +} + +/* Send a raw irccommand to the IRC server. */ +bool irc_send(struct ircstate *state, const char *command, const char *fmt, ...) +{ + va_list ap; + struct irccommand *c = tal(state, struct irccommand); + + c->prefix = NULL; + + if (!state->connected) + return false; + + va_start(ap, fmt); + c->command = tal_strdup(c, command); + c->params = tal_vfmt(c, fmt, ap); + va_end(ap); + + list_add_tail(&state->writequeue, &c->list); + io_wake(state); + return true; +} + +/* Write buffered irccommands to the IRC connection. Commands can be + buffered using irc_send. */ +static struct io_plan *irc_write_loop(struct io_conn *conn, struct ircstate *state) +{ + state->writebuffer = tal_free(state->writebuffer); + + struct irccommand *m = list_pop(&state->writequeue, struct irccommand, list); + if (m == NULL) + return io_out_wait(conn, state, irc_write_loop, state); + + bool hasprefix = m->prefix == NULL; + state->writebuffer = tal_fmt( + state, "%s%s%s %s\r\n", + hasprefix ? "" : m->prefix, + hasprefix ? "" : " ", + m->command, + m->params); + + tal_free(m); + + log_debug(state->log, "Sending: \"%s\"", state->writebuffer); + + return io_write( + conn, + state->writebuffer, strlen(state->writebuffer), + irc_write_loop, state + ); +} + +/* + * 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 + * that has an unhandled type. + */ +static void handle_irc_command(struct ircstate *state, const char *line) +{ + log_debug(state->log, "Received: \"%s\"", line); + + struct irccommand *m = talz(state, struct irccommand); + char** splits = tal_strsplit(m, line, " ", STR_NO_EMPTY); + int numsplits = tal_count(splits) - 1; + + if (numsplits > 2 && strstarts(splits[0], ":")) { + m->prefix = splits[0]; + splits++; + } + m->command = splits[0]; + m->params = tal_strjoin(m, splits + 1, " ", STR_NO_TRAIL); + + if (streq(m->command, "PING")) { + irc_send(state, "PONG", "%s", m->params); + + } else if (streq(m->command, "PRIVMSG")) { + struct privmsg *pm = talz(m, struct privmsg); + pm->sender = m->prefix; + pm->channel = splits[1]; + pm->msg = tal_strjoin(m, splits + 2, " ", STR_NO_TRAIL); + irc_privmsg_cb(state, pm); + } + 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. + */ +static struct io_plan *irc_read_loop(struct io_conn *conn, struct ircstate *state) +{ + + size_t len = state->readlen + state->buffered; + char *start = state->buffer, *end; + + while ((end = memchr(start, '\n', len)) != NULL) { + /* Strip "\r\n" from lines. */ + const char *line = tal_strndup(state, start, end - 1 - start); + handle_irc_command(state, line); + tal_free(line); + len -= (end + 1 - start); + start = end + 1; + } + + /* Move any partial data back down. */ + memmove(state->buffer, start, len); + state->buffered = len; + + return io_read_partial(conn, state->buffer + state->buffered, + sizeof(state->buffer) - state->buffered, + &state->readlen, irc_read_loop, state); +} + +static void irc_failed(struct lightningd_state *dstate, struct ircstate *state) +{ + irc_disconnected(state->conn, state); + state->connected = false; +} + +static void irc_disconnected(struct io_conn *conn, struct ircstate *state) +{ + log_debug(state->log, "Lost connection to IRC server"); + state->connected = false; + state->conn = NULL; + state->readlen = 0; + state->buffered = 0; + memset(state->buffer, 0, sizeof(state->buffer)); + + /* Clear any pending commands, they're no longer useful */ + while (!list_empty(&state->writequeue)) + tal_free(list_pop(&state->writequeue, struct irccommand, list)); + + /* Same goes for partially written commands */ + state->writebuffer = tal_free(state->writebuffer); + + if (irc_disconnect_cb != NULL) + irc_disconnect_cb(state); +} + +void irc_connect(struct ircstate *state) +{ + state->connected = false; + list_head_init(&state->writequeue); + + log_debug(state->log, "Connecting to IRC server %s", state->server); + dns_resolve_and_connect(state->dstate, state->server, "6667", irc_connected, irc_failed, state); +} + +static struct io_plan *irc_connected(struct io_conn *conn, struct lightningd_state *dstate, struct ircstate *state) +{ + io_set_finish(conn, irc_disconnected, state); + state->conn = conn; + 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"); + + return io_duplex(conn, + io_read_partial(conn, + state->buffer, sizeof(state->buffer), + &state->readlen, irc_read_loop, state), + irc_write_loop(conn, state)); +} diff --git a/irc.h b/irc.h new file mode 100644 index 000000000000..6d9cf5f46e3b --- /dev/null +++ b/irc.h @@ -0,0 +1,67 @@ +#ifndef LIGHTNING_IRC_H +#define LIGHTNING_IRC_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "daemon/lightningd.h" + +struct irccommand { + struct list_node list; + const char *prefix; + const char *command; + const char *params; +}; + +struct privmsg { + const char *channel; + const char *sender; + const char *msg; +}; + +struct ircstate { + /* Meta information */ + const char *nick; + const char *server; + + /* Connection and reading */ + struct io_conn *conn; + char buffer[512]; + size_t readlen; + size_t buffered; + + /* Write queue related */ + struct list_head writequeue; + char *writebuffer; + + /* Pointer to external state, making it available to callbacks */ + struct lightningd_state *dstate; + + struct log *log; + + /* Are we currently connected? */ + bool connected; + + /* Time to wait after getting disconnected before reconnecting. */ + struct timerel reconnect_timeout; +}; + +/* Callback to register for incoming messages */ +extern void (*irc_privmsg_cb)(struct ircstate *, const struct privmsg *); +extern void (*irc_disconnect_cb)(struct ircstate *); + +/* Send messages to IRC */ +bool irc_send(struct ircstate *state, const char *command, const char *fmt, ...) PRINTF_FMT(3,4); +bool irc_send_msg(struct ircstate *state, struct privmsg *m); + +/* Register IRC connection with io */ +void irc_connect(struct ircstate *state); + +#endif /* LIGHTNING_IRC_H */ From f4568e5c1c1670763ad5ceb486acb11f14fa7c3e Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 7 Sep 2016 23:34:36 +0200 Subject: [PATCH 2/3] routing: Added IRC announcement glue Added channel announcement serialization and parsing, as well as the entrypoints for the IRC peer discovery. Announcements are signed by the sending endpoint and signatures are verified before adding the channels to the local view of the topology. We do not yet verify the existence of the anchor transaction. --- bitcoin/signature.c | 6 ++ bitcoin/signature.h | 4 ++ daemon/chaintopology.c | 21 ++++++ daemon/chaintopology.h | 12 ++++ daemon/irc_announce.c | 155 +++++++++++++++++++++++++++++++++++++++++ daemon/irc_announce.h | 9 +++ utils.c | 8 +++ utils.h | 4 ++ 8 files changed, 219 insertions(+) create mode 100644 daemon/irc_announce.c create mode 100644 daemon/irc_announce.h diff --git a/bitcoin/signature.c b/bitcoin/signature.c index 001ec2b54f4e..ca3bf40a5279 100644 --- a/bitcoin/signature.c +++ b/bitcoin/signature.c @@ -251,6 +251,12 @@ size_t signature_to_der(secp256k1_context *secpctx, return len; } +bool signature_from_der(secp256k1_context *secpctx, + const u8 *der, size_t len, struct signature *sig) +{ + return secp256k1_ecdsa_signature_parse_der(secpctx, &sig->sig, der, len); +} + /* Signature must have low S value. */ bool sig_valid(secp256k1_context *secpctx, const struct signature *sig) { diff --git a/bitcoin/signature.h b/bitcoin/signature.h index d2251ca94c10..534040c24751 100644 --- a/bitcoin/signature.h +++ b/bitcoin/signature.h @@ -58,4 +58,8 @@ bool sig_valid(secp256k1_context *secpctx, const struct signature *sig); size_t signature_to_der(secp256k1_context *secpctx, u8 der[72], const struct signature *s); +/* Parse DER encoding into signature sig */ +bool signature_from_der(secp256k1_context *secpctx, + const u8 *der, size_t len, struct signature *sig); + #endif /* LIGHTNING_BITCOIN_SIGNATURE_H */ diff --git a/daemon/chaintopology.c b/daemon/chaintopology.c index 87a6880aa02a..684d362de2c3 100644 --- a/daemon/chaintopology.c +++ b/daemon/chaintopology.c @@ -493,6 +493,27 @@ u64 get_feerate(struct lightningd_state *dstate) return dstate->topology->feerate; } +struct txlocator *locate_tx(const void *ctx, struct lightningd_state *dstate, + const struct sha256_double *txid) +{ + struct block *block = block_for_tx(dstate, txid); + if (block == NULL) { + return NULL; + } + + struct txlocator *loc = talz(ctx, struct txlocator); + loc->blkheight = block->height; + + size_t i, n = tal_count(block->txids); + for (i = 0; i < n; i++) { + if (structeq(&block->txids[i], txid)){ + loc->index = i; + break; + } + } + return loc; +} + void setup_topology(struct lightningd_state *dstate) { dstate->topology = tal(dstate, struct topology); diff --git a/daemon/chaintopology.h b/daemon/chaintopology.h index 815c8a110a4f..ac9e9f154479 100644 --- a/daemon/chaintopology.h +++ b/daemon/chaintopology.h @@ -10,6 +10,16 @@ struct peer; struct sha256_double; struct txwatch; +/* Information relevant to locating a TX in a blockchain. */ +struct txlocator { + + /* The height of the block that includes this transaction */ + u32 blkheight; + + /* Position of the transaction in the transactions list */ + u32 index; +}; + /* This is the number of blocks which would have to be mined to invalidate * the tx. */ size_t get_tx_depth(struct lightningd_state *dstate, @@ -33,4 +43,6 @@ void broadcast_tx(struct peer *peer, const struct bitcoin_tx *tx); void setup_topology(struct lightningd_state *dstate); +struct txlocator *locate_tx(const void *ctx, struct lightningd_state *dstate, const struct sha256_double *txid); + #endif /* LIGHTNING_DAEMON_CRYPTOPKT_H */ diff --git a/daemon/irc_announce.c b/daemon/irc_announce.c new file mode 100644 index 000000000000..1b2255aae731 --- /dev/null +++ b/daemon/irc_announce.c @@ -0,0 +1,155 @@ +#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 +#include + +static bool announce_channel(const tal_t *ctx, struct ircstate *state, struct peer *p) +{ + char txid[65]; + int siglen; + u8 der[72]; + struct signature sig; + 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 + ); + + 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); + + irc_send_msg(state, msg); + return true; +} + +static void announce_channels(struct ircstate *state) +{ + tal_t *ctx = tal(state, tal_t); + struct peer *p; + + 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_channels, 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); +} + +/* + * 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) +{ + 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; + if (!signature_from_der(istate->dstate->secpctx, der, siglen, &sig)) + return; + + sha256_double(&hash, content, strlen(content)); + 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; + 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 (!check_signed_hash(istate->dstate->secpctx, &hash, &sig, pk1)) { + 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); +} + +void setup_irc_connection(struct lightningd_state *dstate) +{ + // Register callback + irc_privmsg_cb = *handle_irc_privmsg; + irc_disconnect_cb = *handle_irc_disconnect; + + 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_channels(state); +} diff --git a/daemon/irc_announce.h b/daemon/irc_announce.h new file mode 100644 index 000000000000..bce649771a5f --- /dev/null +++ b/daemon/irc_announce.h @@ -0,0 +1,9 @@ +#ifndef LIGHTNING_DAEMON_IRC_ANNOUNCE_H +#define LIGHTNING_DAEMON_IRC_ANNOUNCE_H +#include "config.h" +#include "irc.h" + +// Main entrypoint for the lightning daemon +void setup_irc_connection(struct lightningd_state *dstate); + +#endif /* LIGHTNING_DAEMON_IRC_ANNOUNCE_H */ diff --git a/utils.c b/utils.c index 86d9790d4316..4058a2bbfbdf 100644 --- a/utils.c +++ b/utils.c @@ -7,3 +7,11 @@ char *tal_hexstr(const tal_t *ctx, const void *data, size_t len) hex_encode(data, len, str, hex_str_size(len)); return str; } + +u8 *tal_hexdata(const tal_t *ctx, const void *str, size_t len) +{ + u8 *data = tal_arr(ctx, u8, hex_data_size(len)); + if (!hex_decode(str, len, data, hex_data_size(len))) + return NULL; + return data; +} diff --git a/utils.h b/utils.h index f89af8c957d8..0fac3c7c8bc9 100644 --- a/utils.h +++ b/utils.h @@ -1,9 +1,13 @@ #ifndef LIGHTNING_UTILS_H #define LIGHTNING_UTILS_H #include "config.h" +#include #include /* Allocate and fill in a hex-encoded string of this data. */ char *tal_hexstr(const tal_t *ctx, const void *data, size_t len); +/* Allocate and fill a buffer with the data of this hex string. */ +u8 *tal_hexdata(const tal_t *ctx, const void *str, size_t len); + #endif /* LIGHTNING_UTILS_H */ From 2f6eefc49b90720255519739fe8d1c3dc7ec1833 Mon Sep 17 00:00:00 2001 From: Christian Decker Date: Wed, 7 Sep 2016 23:47:27 +0200 Subject: [PATCH 3/3] routing: Wiring in the IRC peer discovery Added a flag to disable the IRC discovery and calling the entrypoint to start the IRC client loop. --- daemon/Makefile | 2 ++ daemon/lightningd.c | 11 +++++++++++ daemon/lightningd.h | 3 +++ 3 files changed, 16 insertions(+) diff --git a/daemon/Makefile b/daemon/Makefile index a9d13b5b8cee..60f5cca3fb30 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -26,6 +26,7 @@ DAEMON_SRC := \ daemon/feechange.c \ daemon/htlc.c \ daemon/invoice.c \ + daemon/irc_announce.c \ daemon/jsonrpc.c \ daemon/lightningd.c \ daemon/netaddr.c \ @@ -41,6 +42,7 @@ DAEMON_SRC := \ daemon/wallet.c \ daemon/watch.c \ names.c \ + irc.c \ state.c DAEMON_OBJS := $(DAEMON_SRC:.c=.o) diff --git a/daemon/lightningd.c b/daemon/lightningd.c index 8df13b2dd6d8..fef17f629e62 100644 --- a/daemon/lightningd.c +++ b/daemon/lightningd.c @@ -3,6 +3,7 @@ #include "configdir.h" #include "controlled_time.h" #include "db.h" +#include "irc_announce.h" #include "jsonrpc.h" #include "lightningd.h" #include "log.h" @@ -145,6 +146,9 @@ static void config_register_opts(struct lightningd_state *dstate) dstate, "Add route of form srcid/dstid/base/var/delay/minblocks" "(base in millisatoshi, var in millionths of satoshi per satoshi)"); + opt_register_noarg("--disable-irc", opt_set_invbool, + &dstate->config.use_irc, + "Disable IRC peer discovery for routing"); } static void dev_register_opts(struct lightningd_state *dstate) @@ -210,6 +214,9 @@ static void default_config(struct config *config) config->fee_base = 546000; /* Take 0.001% */ config->fee_per_satoshi = 10; + + /* Discover new peers using IRC */ + config->use_irc = true; } static void check_config(struct lightningd_state *dstate) @@ -360,6 +367,10 @@ int main(int argc, char *argv[]) /* Set up connections from peers. */ setup_listeners(dstate, portnum); + /* set up IRC peer discovery */ + if (dstate->config.use_irc) + setup_irc_connection(dstate); + /* Make sure we use the artificially-controlled time for timers */ io_time_override(controlled_time); diff --git a/daemon/lightningd.h b/daemon/lightningd.h index 88ccd4536931..dc8f8d1624c6 100644 --- a/daemon/lightningd.h +++ b/daemon/lightningd.h @@ -56,6 +56,9 @@ struct config { /* How long between changing commit and sending COMMIT message. */ struct timerel commit_time; + + /* Whether to enable IRC peer discovery. */ + bool use_irc; }; /* Here's where the global variables hide! */