From 8f358b7a91719093a5bd646e7843de5731ff1c3f Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 12 Apr 2017 09:10:10 -0700 Subject: [PATCH] lightningd: add dev_ping command for channeld. Signed-off-by: Rusty Russell --- lightningd/Makefile | 1 + lightningd/channel/channel.c | 64 +++++++++++++++++++- lightningd/channel/channel_wire.csv | 9 +++ lightningd/dev_ping.c | 91 +++++++++++++++++++++++++++++ lightningd/peer_control.c | 2 + lightningd/ping.c | 4 +- lightningd/test/test-ping | 56 ++++++++++++++++++ 7 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 lightningd/dev_ping.c create mode 100755 lightningd/test/test-ping diff --git a/lightningd/Makefile b/lightningd/Makefile index dbc60da5e..0c1455384 100644 --- a/lightningd/Makefile +++ b/lightningd/Makefile @@ -61,6 +61,7 @@ LIGHTNINGD_LIB_HEADERS := $(LIGHTNINGD_LIB_SRC:.c=.h) LIGHTNINGD_SRC := \ lightningd/build_utxos.c \ lightningd/dev_newhtlc.c \ + lightningd/dev_ping.c \ lightningd/gossip_control.c \ lightningd/htlc_end.c \ lightningd/hsm_control.c \ diff --git a/lightningd/channel/channel.c b/lightningd/channel/channel.c index 5db314667..b3c16193d 100644 --- a/lightningd/channel/channel.c +++ b/lightningd/channel/channel.c @@ -87,6 +87,9 @@ struct peer { struct oneshot *commit_timer; u32 commit_msec; + /* Don't accept a pong we didn't ping for. */ + size_t num_pings_outstanding; + /* Announcement related information */ struct pubkey node_ids[NUM_SIDES]; struct short_channel_id short_channel_ids[NUM_SIDES]; @@ -830,10 +833,31 @@ static void handle_ping(struct peer *peer, const u8 *msg) &peer->channel_id, WIRE_CHANNEL_PEER_BAD_MESSAGE, "Bad ping"); + + status_trace("Got ping, sending %s", pong ? + wire_type_name(fromwire_peektype(pong)) + : "nothing"); + if (pong) msg_enqueue(&peer->peer_out, take(pong)); } +static void handle_pong(struct peer *peer, const u8 *pong) +{ + u8 *ignored; + + status_trace("Got pong!"); + if (!fromwire_pong(pong, pong, NULL, &ignored)) + status_failed(WIRE_CHANNEL_PEER_READ_FAILED, "Bad pong"); + + if (!peer->num_pings_outstanding) + status_failed(WIRE_CHANNEL_PEER_READ_FAILED, "Unexpected pong"); + + peer->num_pings_outstanding--; + daemon_conn_send(&peer->master, + take(towire_channel_ping_reply(pong, tal_len(pong)))); +} + static struct io_plan *peer_in(struct io_conn *conn, struct peer *peer, u8 *msg) { enum wire_type type = fromwire_peektype(msg); @@ -886,9 +910,10 @@ static struct io_plan *peer_in(struct io_conn *conn, struct peer *peer, u8 *msg) case WIRE_PING: handle_ping(peer, msg); goto done; - - /* We don't send pings, so don't expect pongs. */ case WIRE_PONG: + handle_pong(peer, msg); + goto done; + case WIRE_INIT: case WIRE_ERROR: case WIRE_OPEN_CHANNEL: @@ -1168,6 +1193,36 @@ static void handle_fail(struct peer *peer, const u8 *inmsg) abort(); } +static void handle_ping_cmd(struct peer *peer, const u8 *inmsg) +{ + u16 num_pong_bytes, ping_len; + u8 *ping; + + if (!fromwire_channel_ping(inmsg, NULL, &num_pong_bytes, &ping_len)) + status_failed(WIRE_CHANNEL_BAD_COMMAND, "Bad channel_ping"); + + ping = make_ping(peer, num_pong_bytes, ping_len); + if (tal_len(ping) > 65535) + status_failed(WIRE_CHANNEL_BAD_COMMAND, "Oversize channel_ping"); + + msg_enqueue(&peer->peer_out, take(ping)); + + status_trace("sending ping expecting %sresponse", + num_pong_bytes >= 65532 ? "no " : ""); + + /* BOLT #1: + * + * if `num_pong_bytes` is less than 65532 it MUST respond by sending a + * `pong` message with `byteslen` equal to `num_pong_bytes`, otherwise + * it MUST ignore the `ping`. + */ + if (num_pong_bytes >= 65532) + daemon_conn_send(&peer->master, + take(towire_channel_ping_reply(peer, 0))); + else + peer->num_pings_outstanding++; +} + static struct io_plan *req_in(struct io_conn *conn, struct daemon_conn *master) { struct peer *peer = container_of(master, struct peer, master); @@ -1193,6 +1248,9 @@ static struct io_plan *req_in(struct io_conn *conn, struct daemon_conn *master) case WIRE_CHANNEL_FAIL_HTLC: handle_fail(peer, master->msg_in); goto out; + case WIRE_CHANNEL_PING: + handle_ping_cmd(peer, master->msg_in); + goto out; case WIRE_CHANNEL_BAD_COMMAND: case WIRE_CHANNEL_HSM_FAILED: @@ -1208,6 +1266,7 @@ static struct io_plan *req_in(struct io_conn *conn, struct daemon_conn *master) case WIRE_CHANNEL_FULFILLED_HTLC: case WIRE_CHANNEL_FAILED_HTLC: case WIRE_CHANNEL_MALFORMED_HTLC: + case WIRE_CHANNEL_PING_REPLY: case WIRE_CHANNEL_PEER_BAD_MESSAGE: break; } @@ -1240,6 +1299,7 @@ int main(int argc, char *argv[]) daemon_conn_init(peer, &peer->master, REQ_FD, req_in); peer->channel = NULL; peer->htlc_id = 0; + peer->num_pings_outstanding = 0; timers_init(&peer->timers, time_mono()); peer->commit_timer = NULL; peer->commit_index[LOCAL] = peer->commit_index[REMOTE] = 0; diff --git a/lightningd/channel/channel_wire.csv b/lightningd/channel/channel_wire.csv index 9e84b78c7..a70c5e6cb 100644 --- a/lightningd/channel/channel_wire.csv +++ b/lightningd/channel/channel_wire.csv @@ -104,3 +104,12 @@ channel_malformed_htlc,10 channel_malformed_htlc,0,id,8 channel_malformed_htlc,0,sha256_of_onion,32 channel_malformed_htlc,0,failure_code,2 + +# Ping/pong test. +channel_ping,11 +channel_ping,0,num_pong_bytes,u16 +channel_ping,0,len,u16 + +channel_ping_reply,111 +channel_ping_reply,0,totlen,u16 + diff --git a/lightningd/dev_ping.c b/lightningd/dev_ping.c new file mode 100644 index 000000000..d17a8bc77 --- /dev/null +++ b/lightningd/dev_ping.c @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool ping_reply(struct subd *subd, const u8 *msg, const int *fds, + struct command *cmd) +{ + u16 totlen; + bool ok; + + log_debug(subd->ld->log, "Got ping reply!"); + ok = fromwire_channel_ping_reply(msg, NULL, &totlen); + + if (!ok) + command_fail(cmd, "Bad reply message"); + else { + struct json_result *response = new_json_result(cmd); + + json_object_start(response, NULL); + json_add_num(response, "totlen", totlen); + json_object_end(response); + command_success(cmd, response); + } + return true; +} + +static void json_dev_ping(struct command *cmd, + const char *buffer, const jsmntok_t *params) +{ + struct lightningd *ld = ld_from_dstate(cmd->dstate); + struct peer *peer; + u8 *msg; + jsmntok_t *peeridtok, *lentok, *pongbytestok; + unsigned int len, pongbytes; + + if (!json_get_params(buffer, params, + "peerid", &peeridtok, + "len", &lentok, + "pongbytes", &pongbytestok, + NULL)) { + command_fail(cmd, "Need peerid, len and pongbytes"); + return; + } + + peer = peer_from_json(ld, buffer, peeridtok); + if (!peer) { + command_fail(cmd, "Could not find peer with that peerid"); + return; + } + + /* FIXME: These checks are horrible, use a peer flag to say it's + * ready to forward! */ + if (peer->owner && !streq(peer->owner->name, "lightningd_channel")) { + command_fail(cmd, "Peer in %s", + peer->owner ? peer->owner->name : "unattached"); + return; + } + + if (!json_tok_number(buffer, lentok, &len)) { + command_fail(cmd, "'%.*s' is not a valid number", + (int)(lentok->end - lentok->start), + buffer + lentok->start); + return; + } + + if (!json_tok_number(buffer, pongbytestok, &pongbytes)) { + command_fail(cmd, "'%.*s' is not a valid number", + (int)(pongbytestok->end - pongbytestok->start), + buffer + pongbytestok->start); + return; + } + + msg = towire_channel_ping(cmd, pongbytes, len); + + /* FIXME: If subdaemon dies? */ + subd_req(peer->owner, peer->owner, take(msg), -1, 0, ping_reply, cmd); +} + +static const struct json_command dev_ping_command = { + "dev-ping", + json_dev_ping, + "Offer {peerid} a ping of length {len} asking for {pongbytes}", + "Returns { totlen: u32 } on success" +}; +AUTODATA(json_command, &dev_ping_command); diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index acd016c0e..aad6ee561 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -864,8 +864,10 @@ static int channel_msg(struct subd *sd, const u8 *msg, const int *unused) case WIRE_CHANNEL_OFFER_HTLC: case WIRE_CHANNEL_FULFILL_HTLC: case WIRE_CHANNEL_FAIL_HTLC: + case WIRE_CHANNEL_PING: /* Replies go to requests. */ case WIRE_CHANNEL_OFFER_HTLC_REPLY: + case WIRE_CHANNEL_PING_REPLY: break; } diff --git a/lightningd/ping.c b/lightningd/ping.c index 9a9e9ca8c..ffdf766ad 100644 --- a/lightningd/ping.c +++ b/lightningd/ping.c @@ -33,7 +33,9 @@ bool check_ping_make_pong(const tal_t *ctx, const u8 *ping, u8 **pong) ignored = tal_arrz(ctx, u8, num_pong_bytes); *pong = towire_pong(ctx, ignored); tal_free(ignored); - } + } else + *pong = NULL; + return true; } diff --git a/lightningd/test/test-ping b/lightningd/test/test-ping new file mode 100755 index 000000000..c25c2b077 --- /dev/null +++ b/lightningd/test/test-ping @@ -0,0 +1,56 @@ +#! /bin/sh -e + +# Wherever we are, we want to be in daemon/test dir. +cd `git rev-parse --show-toplevel`/daemon/test + +add_funds() +{ + local NEWADDR=`$1 newaddr | get_field address` + local FUND_INPUT_TXID=`$CLI sendtoaddress $NEWADDR $2` + local FUND_INPUT_TX=`$CLI getrawtransaction $FUND_INPUT_TXID` + $1 addfunds $FUND_INPUT_TX +} + +. scripts/vars.sh +. scripts/helpers.sh + +parse_cmdline 2 "$@" +setup_lightning 2 +start_lightningd 2 lightningd/lightningd + +lcli1 connect localhost $PORT2 $ID2 + +add_funds lcli1 0.2 + +# Now fund the channels +CHANNEL_SAT=10000000 +CHANNEL_MSAT=$(($CHANNEL_SAT * 1000)) +lcli1 fundchannel $ID2 $CHANNEL_SAT + +# Lock them in. +$CLI generate 10 + +check "lcli1 getpeers info | $FGREP 'Funding tx reached depth'" + +# 0-byte pong gives just type + length field. +[ `lcli1 dev-ping $ID2 0 0 | get_field totlen` = 4 ] + +# 1000-byte ping, 0-byte pong. +[ `lcli1 dev-ping $ID2 1000 0 | get_field totlen` = 4 ] + +# 1000 byte pong. +[ `lcli1 dev-ping $ID2 1000 1000 | get_field totlen` = 1004 ] + +# Maximum length pong. +[ `lcli1 dev-ping $ID2 1000 65531 | get_field totlen` = 65535 ] + +# Overlength -> no reply. +[ `lcli1 dev-ping $ID2 1000 65532 | get_field totlen` = 0 ] +[ `lcli1 dev-ping $ID2 1000 65533 | get_field totlen` = 0 ] +[ `lcli1 dev-ping $ID2 1000 65534 | get_field totlen` = 0 ] +[ `lcli1 dev-ping $ID2 1000 65535 | get_field totlen` = 0 ] + +lcli1 stop +lcli2 stop + +all_ok