diff --git a/devtools/.gitignore b/devtools/.gitignore index eb53afe43..0019a5abf 100644 --- a/devtools/.gitignore +++ b/devtools/.gitignore @@ -2,3 +2,4 @@ bolt11-cli decodemsg onion dump-gossipstore +gossipwith diff --git a/devtools/Makefile b/devtools/Makefile index c2af6a2a7..e70db606b 100644 --- a/devtools/Makefile +++ b/devtools/Makefile @@ -1,6 +1,7 @@ DEVTOOLS_SRC := devtools/gen_print_wire.c devtools/gen_print_onion_wire.c devtools/print_wire.c DEVTOOLS_OBJS := $(DEVTOOLS_SRC:.c=.o) -DEVTOOLS_TOOL_SRC := devtools/bolt11-cli.c devtools/decodemsg.c devtools/onion.c devtools/dump-gossipstore.c +DEVTOOLS := devtools/bolt11-cli devtools/decodemsg devtools/onion devtools/dump-gossipstore devtools/gossipwith +DEVTOOLS_TOOL_SRC := $(DEVTOOLS:=.c) DEVTOOLS_TOOL_OBJS := $(DEVTOOLS_TOOL_SRC:.c=.o) DEVTOOLS_COMMON_OBJS := \ @@ -15,7 +16,7 @@ DEVTOOLS_COMMON_OBJS := \ common/version.o \ common/wireaddr.o -devtools-all: devtools/bolt11-cli devtools/decodemsg devtools/onion devtools/dump-gossipstore +devtools-all: $(DEVTOOLS) devtools/gen_print_wire.h: $(WIRE_GEN) wire/gen_peer_wire_csv $(WIRE_GEN) --bolt --printwire --header $@ wire_type < wire/gen_peer_wire_csv > $@ @@ -40,12 +41,16 @@ devtools/onion.c: ccan/config.h devtools/onion: $(DEVTOOLS_OBJS) $(DEVTOOLS_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o devtools/onion.o common/sphinx.o +devtools/gossipwith: $(DEVTOOLS_OBJS) $(DEVTOOLS_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o wire/gen_peer_wire.o devtools/gossipwith.o common/cryptomsg.o common/cryptomsg.o common/crypto_sync.o + $(DEVTOOLS_OBJS) $(DEVTOOLS_TOOL_OBJS): wire/wire.h devtools/gen_print_wire.h devtools/gen_print_onion_wire.h devtools/gen_print_wire.o: devtools/gen_print_wire.h wire/gen_peer_wire.h devtools/print_wire.h devtools/gen_print_onion_wire.o: devtools/gen_print_onion_wire.h devtools/print_wire.h +devtools/bolt11-cli: $(DEVTOOLS_OBJS) $(DEVTOOLS_COMMON_OBJS) $(JSMN_OBJS) $(CCAN_OBJS) $(BITCOIN_OBJS) wire/fromwire.o wire/towire.o devtools/bolt11-cli.o + # Make sure these depend on everything. -ALL_PROGRAMS += devtools/bolt11-cli devtools/decodemsg devtools/onion devtools/dump-gossipstore +ALL_PROGRAMS += $(DEVTOOLS) ALL_OBJS += $(DEVTOOLS_OBJS) $(DEVTOOLS_TOOL_OBJS) check-source: $(DEVTOOLS_SRC:%=check-src-include-order/%) $(DEVTOOLS_TOOLS_SRC:%=check-src-include-order/%) diff --git a/devtools/gossipwith.c b/devtools/gossipwith.c new file mode 100644 index 000000000..a97099814 --- /dev/null +++ b/devtools/gossipwith.c @@ -0,0 +1,220 @@ +/* Simple tool to route gossip from a peer. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define io_write_ simple_write +#define io_read_ simple_read + +static struct io_plan *simple_write(struct io_conn *conn, + const void *data, size_t len, + struct io_plan *(*next)(struct io_conn *, void *), + void *arg); + +static struct io_plan *simple_read(struct io_conn *conn, + void *data, size_t len, + struct io_plan *(*next)(struct io_conn *, void *), + void *next_arg); + +#include "../connectd/handshake.c" + +/* This makes the handshake prototypes work. */ +struct io_conn { + int fd; +}; + +static struct secret notsosecret; +static bool initial_sync = false; +static unsigned long max_messages = -1UL; + +/* Empty stubs to make us compile */ +void status_peer_io(enum log_level iodir, const u8 *p) +{ +} + +void status_fmt(enum log_level level, const char *fmt, ...) +{ +} + +#if DEVELOPER +void dev_sabotage_fd(int fd) +{ + abort(); +} + +void dev_blackhole_fd(int fd) +{ + abort(); +} + +enum dev_disconnect dev_disconnect(int pkt_type) +{ + return DEV_DISCONNECT_NORMAL; +} +#endif + +void peer_failed_connection_lost(void) +{ + exit(0); +} + +bool hsm_do_ecdh(struct secret *ss, const struct pubkey *point) +{ + if (secp256k1_ecdh(secp256k1_ctx, ss->data, &point->pubkey, + notsosecret.data) != 1) + errx(1, "ECDH failed"); + return true; +} + +/* We don't want to discard *any* messages. */ +bool is_unknown_msg_discardable(const u8 *cursor) +{ + return false; +} + +static struct io_plan *simple_write(struct io_conn *conn, + const void *data, size_t len, + struct io_plan *(*next)(struct io_conn *, void *), + void *arg) +{ + if (!write_all(conn->fd, data, len)) + err(1, "Writing data"); + return next(conn, arg); +} + +static struct io_plan *simple_read(struct io_conn *conn, + void *data, size_t len, + struct io_plan *(*next)(struct io_conn *, void *), + void *next_arg) +{ + if (!read_all(conn->fd, data, len)) + err(1, "Reading data"); + return next(conn, next_arg); +} + +static struct io_plan *handshake_success(struct io_conn *conn, + const struct pubkey *them, + const struct wireaddr_internal *addr, + const struct crypto_state *orig_cs, + void *unused) +{ + u8 *msg; + struct crypto_state cs = *orig_cs; + u8 *local_features; + + if (initial_sync) { + local_features = tal(conn, u8); + local_features[0] = (1 << 3); + } else + local_features = NULL; + + msg = towire_init(NULL, NULL, local_features); + + sync_crypto_write(&cs, conn->fd, take(msg)); + /* Ignore their init message. */ + tal_free(sync_crypto_read(NULL, &cs, conn->fd)); + + /* Now write out whatever we get. */ + while ((msg = sync_crypto_read(NULL, &cs, conn->fd)) != NULL) { + be16 len = cpu_to_be16(tal_bytelen(msg)); + + if (!write_all(STDOUT_FILENO, &len, sizeof(len)) + || !write_all(STDOUT_FILENO, msg, tal_bytelen(msg))) + err(1, "Writing out msg"); + tal_free(msg); + + if (--max_messages == 0) + exit(0); + } + err(1, "Reading msg"); +} + +int main(int argc, char *argv[]) +{ + struct io_conn *conn = tal(NULL, struct io_conn); + struct wireaddr_internal addr; + int af; + struct pubkey us, them; + const char *err_msg; + const char *at; + struct addrinfo *ai; + + setup_locale(); + secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | + SECP256K1_CONTEXT_SIGN); + + opt_register_noarg("--initial-sync", opt_set_bool, &initial_sync, + "Stream complete gossip history at start"); + opt_register_arg("--max-messages", opt_set_ulongval, opt_show_ulongval, + &max_messages, + "Terminate after reading this many messages (> 0)"); + opt_register_noarg("--help|-h", opt_usage_and_exit, + "id@addr[:port]\n" + "Connect to a lightning peer and relay gossip messages from it", + "Print this message."); + + opt_parse(&argc, argv, opt_log_stderr_exit); + if (argc != 2) + opt_usage_exit_fail("Need an id@addr to connect to"); + at = strchr(argv[1], '@'); + if (!at) + opt_usage_exit_fail("Need id@addr"); + + if (!pubkey_from_hexstr(argv[1], at - argv[1], &them)) + opt_usage_exit_fail("Invalid id %.*s", + (int)(at - argv[1]), argv[1]); + + if (!parse_wireaddr_internal(at+1, &addr, DEFAULT_PORT, NULL, + true, false, &err_msg)) + opt_usage_exit_fail("%s '%s'", err_msg, argv[1]); + + switch (addr.itype) { + case ADDR_INTERNAL_SOCKNAME: + af = AF_LOCAL; + ai = wireaddr_internal_to_addrinfo(conn, &addr); + break; + case ADDR_INTERNAL_ALLPROTO: + case ADDR_INTERNAL_AUTOTOR: + case ADDR_INTERNAL_FORPROXY: + opt_usage_exit_fail("Don't support proxy use"); + + case ADDR_INTERNAL_WIREADDR: + switch (addr.u.wireaddr.type) { + case ADDR_TYPE_TOR_V2: + case ADDR_TYPE_TOR_V3: + opt_usage_exit_fail("Don't support proxy use"); + break; + case ADDR_TYPE_IPV4: + af = AF_INET; + break; + case ADDR_TYPE_IPV6: + af = AF_INET6; + break; + case ADDR_TYPE_PADDING: + abort(); + } + ai = wireaddr_to_addrinfo(tmpctx, &addr.u.wireaddr); + } + conn->fd = socket(af, SOCK_STREAM, 0); + if (conn->fd < 0) + err(1, "Creating socket"); + + memset(¬sosecret, 0x42, sizeof(notsosecret)); + if (!pubkey_from_secret(¬sosecret, &us)) + errx(1, "Creating pubkey"); + + if (connect(conn->fd, ai->ai_addr, ai->ai_addrlen) != 0) + err(1, "Connecting to %s", at+1); + + initiator_handshake(conn, &us, &them, &addr, handshake_success, NULL); + exit(0); +} + diff --git a/tests/test_gossip.py b/tests/test_gossip.py index ce53d1ac3..98c40f442 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -1,9 +1,10 @@ from fixtures import * # noqa: F401,F403 -from utils import wait_for +from utils import wait_for, TIMEOUT import json import logging import os +import struct import subprocess import time import unittest @@ -839,3 +840,25 @@ def test_gossip_store_load(node_factory): # May preceed the Started msg waited for in 'start'. wait_for(lambda: l1.daemon.is_in_log('gossip_store: Read 1/1/1/0 cannounce/cupdate/nannounce/cdelete from store in 744 bytes')) assert not l1.daemon.is_in_log('gossip_store.*truncating') + + +def test_gossipwith(node_factory): + l1, l2 = node_factory.line_graph(2, announce=True) + + out = subprocess.run(['devtools/gossipwith', + '--initial-sync', + '--max-messages=5', + '{}@localhost:{}'.format(l1.info['id'], l1.port)], + check=True, + timeout=TIMEOUT, stdout=subprocess.PIPE).stdout + + num_msgs = 0 + while len(out): + l, t = struct.unpack('>HH', out[0:4]) + # channel_announcement node_announcement or channel_update + assert t == 256 or t == 257 or t == 258 + out = out[2 + l:] + num_msgs += 1 + + # one channel announcement, two channel_updates, two node announcements. + assert num_msgs == 5