diff --git a/channeld/channeld.c b/channeld/channeld.c index 30ae01f84190..8d083e3552ca 100644 --- a/channeld/channeld.c +++ b/channeld/channeld.c @@ -55,6 +55,7 @@ #include #include #include +#include #include #include #include @@ -1817,6 +1818,27 @@ static void handle_peer_shutdown(struct peer *peer, const u8 *shutdown) billboard_update(peer); } +/* Try to handle a custommsg Returns true if it was a custom message and has + * been handled, false if the message was not handled. + */ +static bool channeld_handle_custommsg(const u8 *msg) +{ +#if DEVELOPER + enum wire_type type = fromwire_peektype(msg); + if (type % 2 == 1 && !wire_type_is_defined(type)) { + /* The message is not part of the messages we know how to + * handle. Assuming this is a custommsg, we just forward it to the + * master. */ + wire_sync_write(MASTER_FD, take(towire_custommsg_in(NULL, msg))); + return true; + } else { + return false; + } +#else + return false; +#endif +} + static void peer_in(struct peer *peer, const u8 *msg) { enum wire_type type = fromwire_peektype(msg); @@ -1829,6 +1851,9 @@ static void peer_in(struct peer *peer, const u8 *msg) return; } + if (channeld_handle_custommsg(msg)) + return; + /* Since LND seems to send errors which aren't actually fatal events, * we treat errors here as soft. */ if (handle_peer_gossip_or_error(peer->pps, &peer->channel_id, true, msg)) @@ -2329,9 +2354,10 @@ static void peer_reconnect(struct peer *peer, do { clean_tmpctx(); msg = sync_crypto_read(tmpctx, peer->pps); - } while (handle_peer_gossip_or_error(peer->pps, &peer->channel_id, true, - msg) - || capture_premature_msg(&premature_msgs, msg)); + } while (channeld_handle_custommsg(msg) || + handle_peer_gossip_or_error(peer->pps, &peer->channel_id, true, + msg) || + capture_premature_msg(&premature_msgs, msg)); if (peer->channel->option_static_remotekey) { struct pubkey ignore; @@ -2851,6 +2877,14 @@ static void handle_dev_memleak(struct peer *peer, const u8 *msg) take(towire_channel_dev_memleak_reply(NULL, found_leak))); } + +/* We were told to send a custommsg to the peer by `lightningd`. All the + * verification is done on the side of `lightningd` so we should be good to + * just forward it here. */ +static void channeld_send_custommsg(struct peer *peer, const u8 *msg) +{ + sync_crypto_write(peer->pps, take(msg)); +} #endif /* DEVELOPER */ static void req_in(struct peer *peer, const u8 *msg) @@ -2911,6 +2945,21 @@ static void req_in(struct peer *peer, const u8 *msg) case WIRE_CHANNEL_SEND_ERROR_REPLY: break; } + + /* Now handle common messages. */ + switch ((enum common_wire_type)t) { +#if DEVELOPER + case WIRE_CUSTOMMSG_OUT: + channeld_send_custommsg(peer, msg); + return; +#else + case WIRE_CUSTOMMSG_OUT: +#endif + /* We send these. */ + case WIRE_CUSTOMMSG_IN: + break; + } + master_badmsg(-1, msg); } diff --git a/common/read_peer_msg.c b/common/read_peer_msg.c index f6c74e1dfbcb..da52b903e55b 100644 --- a/common/read_peer_msg.c +++ b/common/read_peer_msg.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -155,6 +156,11 @@ bool handle_peer_gossip_or_error(struct per_peer_state *pps, bool all_channels; struct channel_id actual; +#if DEVELOPER + /* Any odd-typed unknown message is handled by the caller, so if we + * find one here it's an error. */ + assert(!is_unknown_msg_discardable(msg)); +#else /* BOLT #1: * * A receiving node: @@ -163,6 +169,7 @@ bool handle_peer_gossip_or_error(struct per_peer_state *pps, */ if (is_unknown_msg_discardable(msg)) goto handled; +#endif if (handle_timestamp_filter(pps, msg)) return true; diff --git a/doc/Makefile b/doc/Makefile index db2b300293a7..dc5738e59a77 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -16,6 +16,7 @@ MANPAGES := doc/lightning-cli.1 \ doc/lightning-decodepay.7 \ doc/lightning-delexpiredinvoice.7 \ doc/lightning-delinvoice.7 \ + doc/lightning-dev-sendcustommsg.7 \ doc/lightning-disconnect.7 \ doc/lightning-fundchannel.7 \ doc/lightning-fundchannel_start.7 \ diff --git a/doc/PLUGINS.md b/doc/PLUGINS.md index 5a987da8af15..1458c0b4ef35 100644 --- a/doc/PLUGINS.md +++ b/doc/PLUGINS.md @@ -760,8 +760,43 @@ Return a custom error to the request sender: } ``` + +#### `custommsg` + +The `custommsg` plugin hook is the receiving counterpart to the +[`dev-sendcustommsg`][sendcustommsg] RPC method and allows plugins to handle +messages that are not handled internally. The goal of these two components is +to allow the implementation of custom protocols or prototypes on top of a +c-lightning node, without having to change the node's implementation itself. + +The payload for a call follows this format: + +```json +{ + "peer_id": "02df5ffe895c778e10f7742a6c5b8a0cefbe9465df58b92fadeb883752c8107c8f", + "message": "1337ffffffff" +} +``` + +This payload would have been sent by the peer with the `node_id` matching +`peer_id`, and the message has type `0x1337` and contents `ffffffff`. Notice +that the messages are currently limited to odd-numbered types and must not +match a type that is handled internally by c-lightning. These limitations are +in place in order to avoid conflicts with the internal state tracking, and +avoiding disconnections or channel closures, since odd-numbered message can be +ignored by nodes (see ["it's ok to be odd" in the specification][oddok] for +details). The plugin must implement the parsing of the message, including the +type prefix, since c-lightning does not know how to parse the message. + +The result for this hook is currently being discarded. For future uses of the +result we suggest just returning a `null`. This will ensure backward +compatibility should the semantics be changed in future. + + [jsonrpc-spec]: https://www.jsonrpc.org/specification [jsonrpc-notification-spec]: https://www.jsonrpc.org/specification#notification [bolt4]: https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md [bolt4-failure-codes]: https://github.com/lightningnetwork/lightning-rfc/blob/master/04-onion-routing.md#failure-messages [bolt2-open-channel]: https://github.com/lightningnetwork/lightning-rfc/blob/master/02-peer-protocol.md#the-open_channel-message +[sendcustommsg]: lightning-dev-sendcustommsg.7.html +[oddok]: https://github.com/lightningnetwork/lightning-rfc/blob/master/00-introduction.md#its-ok-to-be-odd diff --git a/doc/index.rst b/doc/index.rst index e6380274fee3..325a02607e59 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -39,6 +39,7 @@ c-lightning Documentation lightning-decodepay lightning-delexpiredinvoice lightning-delinvoice + lightning-dev-sendcustommsg lightning-disconnect lightning-fundchannel lightning-fundchannel_cancel diff --git a/doc/lightning-dev-sendcustommsg.7 b/doc/lightning-dev-sendcustommsg.7 new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/doc/lightning-dev-sendcustommsg.7.md b/doc/lightning-dev-sendcustommsg.7.md new file mode 100644 index 000000000000..dfcbc17b5e2e --- /dev/null +++ b/doc/lightning-dev-sendcustommsg.7.md @@ -0,0 +1,63 @@ +lightning-dev-sendcustommsg -- Low-level interface to send protocol messages to peers +===================================================================================== + +SYNOPSIS +-------- + +**dev-sendcustommsg** *node_id* *msg* + +DESCRIPTION +----------- + +The `dev-sendcustommsg` RPC method allows the user to inject a custom message +into the communication with the peer with the given `node_id`. This is +intended as a low-level interface to implement custom protocol extensions on +top, not for direct use by end-users. + +The message must be a hex encoded well-formed message, including the 2-byte +type prefix, but excluding the length prefix which will be added by the RPC +method. The messages must not use even-numbered types, since these may require +synchronous handling on the receiving side, and can cause the connection to be +dropped. The message types may also not use one of the internally handled +types, since that may cause issues with the internal state tracking of +c-lightning. + +The node specified by `node_id` must be a peer, i.e., it must have a direct +connection with the node receiving the RPC call, and the connection must be +established. For a method to send arbitrary messages over multiple hops, +including hops that do not understand the custom message, see the +`createonion` and `sendonion` RPC methods. Messages can only be injected if +the connection is handled by `openingd` or `channeld`. Messages cannot be +injected when the peer is handled by `onchaind` or `closingd` since these do +not have a connection, or are synchronous daemons that do not handle +spontaneous messages. + +On the reveiving end a plugin may implement the `custommsg` plugin hook and +get notified about incoming messages. + +RETURN VALUE +------------ + +The method will validate the arguments and queue the message for delivery +through the daemon that is currently handling the connection. Queuing provides +best effort guarantees and the message may not be delivered if the connection +is terminated while the message is queued. The RPC method will return as soon +as the message is queued. + +If any of the above limitations is not respected the method returns an +explicit error message stating the issue. + +AUTHOR +------ + +Christian Decker <> is mainly responsible. + +SEE ALSO +-------- + +lightning-createonion(7), lightning-sendonion(7) + +RESOURCES +--------- + +Main web site: diff --git a/lightningd/channel_control.c b/lightningd/channel_control.c index f59d56795b50..1cb3e84e4c98 100644 --- a/lightningd/channel_control.c +++ b/lightningd/channel_control.c @@ -23,6 +23,7 @@ #include #include #include +#include #include static void update_feerates(struct lightningd *ld, struct channel *channel) @@ -319,6 +320,19 @@ static unsigned channel_msg(struct subd *sd, const u8 *msg, const int *fds) break; } + switch ((enum common_wire_type)t) { +#if DEVELOPER + case WIRE_CUSTOMMSG_IN: + handle_custommsg_in(sd->ld, sd->node_id, msg); + break; +#else + case WIRE_CUSTOMMSG_IN: +#endif + /* We send these. */ + case WIRE_CUSTOMMSG_OUT: + break; + } + return 0; } diff --git a/lightningd/opening_control.c b/lightningd/opening_control.c index 614293484ed8..810a45fe57d9 100644 --- a/lightningd/opening_control.c +++ b/lightningd/opening_control.c @@ -29,10 +29,10 @@ #include #include #include -#include #include #include #include +#include #include #include @@ -922,6 +922,20 @@ static unsigned int openingd_msg(struct subd *openingd, case WIRE_OPENING_DEV_MEMLEAK_REPLY: break; } + + switch ((enum common_wire_type)t) { +#if DEVELOPER + case WIRE_CUSTOMMSG_IN: + handle_custommsg_in(openingd->ld, openingd->node_id, msg); + return 0; +#else + case WIRE_CUSTOMMSG_IN: +#endif + /* We send these. */ + case WIRE_CUSTOMMSG_OUT: + break; + } + log_broken(openingd->log, "Unexpected msg %s: %s", opening_wire_type_name(t), tal_hex(tmpctx, msg)); tal_free(openingd); @@ -1323,3 +1337,16 @@ void opening_dev_memleak(struct command *cmd) opening_memleak_req_next(cmd, NULL); } #endif /* DEVELOPER */ + +struct subd *peer_get_owning_subd(struct peer *peer) +{ + struct channel *channel; + channel = peer_active_channel(peer); + + if (channel != NULL) { + return channel->owner; + } else if (peer->uncommitted_channel != NULL) { + return peer->uncommitted_channel->openingd; + } + return NULL; +} diff --git a/lightningd/opening_control.h b/lightningd/opening_control.h index 23df19eb3aee..fbf8ce7156a2 100644 --- a/lightningd/opening_control.h +++ b/lightningd/opening_control.h @@ -2,6 +2,7 @@ #define LIGHTNING_LIGHTNINGD_OPENING_CONTROL_H #include "config.h" #include +#include struct channel_id; struct crypto_state; @@ -26,4 +27,6 @@ struct command; void opening_dev_memleak(struct command *cmd); #endif +struct subd *peer_get_owning_subd(struct peer *peer); + #endif /* LIGHTNING_LIGHTNINGD_OPENING_CONTROL_H */ diff --git a/lightningd/peer_control.c b/lightningd/peer_control.c index 4116dae7b16e..bda3ff4741e8 100644 --- a/lightningd/peer_control.c +++ b/lightningd/peer_control.c @@ -54,6 +54,7 @@ #include #include #include +#include #include #include @@ -2373,5 +2374,132 @@ void peer_dev_memleak(struct command *cmd) { peer_memleak_req_next(cmd, NULL); } + +struct custommsg_payload { + struct node_id peer_id; + const u8 *msg; +}; + +static void custommsg_callback(struct custommsg_payload *payload, + const char *buffer, const jsmntok_t *toks) +{ + tal_free(payload); +} + +static void custommsg_payload_serialize(struct custommsg_payload *payload, + struct json_stream *stream) +{ + json_add_hex_talarr(stream, "message", payload->msg); + json_add_node_id(stream, "peer_id", &payload->peer_id); +} + +REGISTER_PLUGIN_HOOK(custommsg, custommsg_callback, struct custommsg_payload *, + custommsg_payload_serialize, struct custommsg_payload *); + +void handle_custommsg_in(struct lightningd *ld, const struct node_id *peer_id, + const u8 *msg) +{ + struct custommsg_payload *p = tal(NULL, struct custommsg_payload); + u8 *custommsg; + + if (!fromwire_custommsg_in(NULL, msg, &custommsg)) { + log_broken(ld->log, "Malformed custommsg from peer %s: %s", + type_to_string(tmpctx, struct node_id, peer_id), + tal_hex(tmpctx, msg)); + return; + } + + p->peer_id = *peer_id; + p->msg = tal_steal(p, custommsg); + plugin_hook_call_custommsg(ld, p, p); +} + +static struct command_result *json_sendcustommsg(struct command *cmd, + const char *buffer, + const jsmntok_t *obj UNNEEDED, + const jsmntok_t *params) +{ + struct json_stream *response; + struct node_id *dest; + struct peer *peer; + struct subd *owner; + u8 *msg; + int type; + + if (!param(cmd, buffer, params, + p_req("node_id", param_node_id, &dest), + p_req("msg", param_bin_from_hex, &msg), + NULL)) + return command_param_failed(); + + type = fromwire_peektype(msg); + if (wire_type_is_defined(type)) { + return command_fail( + cmd, JSONRPC2_INVALID_REQUEST, + "Cannot send messages of type %d (%s). It is not possible " + "to send messages that have a type managed internally " + "since that might cause issues with the internal state " + "tracking.", + type, wire_type_name(type)); + } + + if (type % 2 == 0) { + return command_fail( + cmd, JSONRPC2_INVALID_REQUEST, + "Cannot send even-typed %d custom message. Currently " + "custom messages are limited to odd-numbered message " + "types, as even-numbered types might result in " + "disconnections.", + type); + } + + peer = peer_by_id(cmd->ld, dest); + if (!peer) { + return command_fail(cmd, JSONRPC2_INVALID_REQUEST, + "No such peer: %s", + type_to_string(cmd, struct node_id, dest)); + } + + owner = peer_get_owning_subd(peer); + if (owner == NULL) { + return command_fail(cmd, JSONRPC2_INVALID_REQUEST, + "Peer is not connected: %s", + type_to_string(cmd, struct node_id, dest)); + } + + /* Only a couple of subdaemons have the ability to send custom + * messages. We whitelist those, and error if the current owner is not + * in the whitelist. The reason is that some subdaemons do not handle + * spontaneous messages from the master well (I'm looking at you + * `closingd`...). */ + if (!streq(owner->name, "channeld") && + !streq(owner->name, "openingd")) { + return command_fail(cmd, JSONRPC2_INVALID_REQUEST, + "Peer is currently owned by %s which does " + "not support injecting custom messages.", + owner->name); + } + + subd_send_msg(owner, take(towire_custommsg_out(cmd, msg))); + + response = json_stream_success(cmd); + json_add_string(response, "status", + tal_fmt(cmd, + "Message sent to subdaemon %s for delivery", + owner->name)); + + return command_success(cmd, response); +} + +static const struct json_command sendcustommsg_command = { + "dev-sendcustommsg", + "utility", + json_sendcustommsg, + "Send a custom message to the peer with the given {node_id}", + .verbose = "dev-sendcustommsg node_id hexcustommsg", +}; + +AUTODATA(json_command, &sendcustommsg_command); + #endif /* DEVELOPER */ diff --git a/lightningd/peer_control.h b/lightningd/peer_control.h index 100756699027..0d53760a9c9a 100644 --- a/lightningd/peer_control.h +++ b/lightningd/peer_control.h @@ -93,6 +93,8 @@ struct htlc_in_map *load_channels_from_wallet(struct lightningd *ld); #if DEVELOPER void peer_dev_memleak(struct command *cmd); +void handle_custommsg_in(struct lightningd *ld, const struct node_id *peer_id, + const u8 *msg); #endif /* DEVELOPER */ /* Triggered at each new block. */ diff --git a/lightningd/subd.c b/lightningd/subd.c index 1c1779cbfbf9..3db01522d6bf 100644 --- a/lightningd/subd.c +++ b/lightningd/subd.c @@ -27,6 +27,7 @@ #include #include #include +#include #include static bool move_fd(int from, int to) @@ -741,9 +742,11 @@ struct subd *new_channel_subd_(struct lightningd *ld, void subd_send_msg(struct subd *sd, const u8 *msg_out) { + u16 type = fromwire_peektype(msg_out); /* FIXME: We should use unique upper bits for each daemon, then * have generate-wire.py add them, just assert here. */ - assert(!strstarts(sd->msgname(fromwire_peektype(msg_out)), "INVALID")); + assert(!strstarts(common_wire_type_name(type), "INVALID") || + !strstarts(sd->msgname(type), "INVALID")); msg_enqueue(sd->outq, msg_out); } diff --git a/lightningd/test/run-invoice-select-inchan.c b/lightningd/test/run-invoice-select-inchan.c index f49a19f30c19..367b79b4fe5b 100644 --- a/lightningd/test/run-invoice-select-inchan.c +++ b/lightningd/test/run-invoice-select-inchan.c @@ -330,6 +330,11 @@ struct command_result *param_array(struct command *cmd UNNEEDED, const char *nam const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, const jsmntok_t **arr UNNEEDED) { fprintf(stderr, "param_array called!\n"); abort(); } +/* Generated stub for param_bin_from_hex */ +struct command_result *param_bin_from_hex(struct command *cmd UNNEEDED, const char *name UNNEEDED, + const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + u8 **bin UNNEEDED) +{ fprintf(stderr, "param_bin_from_hex called!\n"); abort(); } /* Generated stub for param_bitcoin_address */ struct command_result *param_bitcoin_address(struct command *cmd UNNEEDED, const char *name UNNEEDED, @@ -407,6 +412,9 @@ struct command_result *param_u64(struct command *cmd UNNEEDED, const char *name const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, uint64_t **num UNNEEDED) { fprintf(stderr, "param_u64 called!\n"); abort(); } +/* Generated stub for peer_get_owning_subd */ +struct subd *peer_get_owning_subd(struct peer *peer UNNEEDED) +{ fprintf(stderr, "peer_get_owning_subd called!\n"); abort(); } /* Generated stub for peer_memleak_done */ void peer_memleak_done(struct command *cmd UNNEEDED, struct subd *leaker UNNEEDED) { fprintf(stderr, "peer_memleak_done called!\n"); abort(); } diff --git a/openingd/openingd.c b/openingd/openingd.c index 3b9e5258e4db..82004735613d 100644 --- a/openingd/openingd.c +++ b/openingd/openingd.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -1281,6 +1282,18 @@ static u8 *handle_peer_in(struct state *state) if (t == WIRE_OPEN_CHANNEL) return fundee_channel(state, msg); +#if DEVELOPER + /* Handle custommsgs */ + enum wire_type type = fromwire_peektype(msg); + if (type % 2 == 1 && !wire_type_is_defined(type)) { + /* The message is not part of the messages we know how to + * handle. Assuming this is a custommsg, we just forward it to the + * master. */ + wire_sync_write(REQ_FD, take(towire_custommsg_in(NULL, msg))); + return NULL; + } +#endif + /* Handles standard cases, and legal unknown ones. */ if (handle_peer_gossip_or_error(state->pps, &state->channel_id, false, msg)) @@ -1354,6 +1367,14 @@ static void handle_dev_memleak(struct state *state, const u8 *msg) take(towire_opening_dev_memleak_reply(NULL, found_leak))); } + +/* We were told to send a custommsg to the peer by `lightningd`. All the + * verification is done on the side of `lightningd` so we should be good to + * just forward it here. */ +static void openingd_send_custommsg(struct state *state, const u8 *msg) +{ + sync_crypto_write(state->pps, take(msg)); +} #endif /* DEVELOPER */ /* Standard lightningd-fd-is-ready-to-read demux code. Again, we could hang @@ -1413,6 +1434,20 @@ static u8 *handle_master_in(struct state *state) break; } + /* Now handle common messages. */ + switch ((enum common_wire_type)t) { +#if DEVELOPER + case WIRE_CUSTOMMSG_OUT: + openingd_send_custommsg(state, msg); + return NULL; +#else + case WIRE_CUSTOMMSG_OUT: +#endif + /* We send these. */ + case WIRE_CUSTOMMSG_IN: + break; + } + status_failed(STATUS_FAIL_MASTER_IO, "Unknown msg %s", tal_hex(tmpctx, msg)); } diff --git a/tests/plugins/custommsg.py b/tests/plugins/custommsg.py new file mode 100755 index 000000000000..3c99b03660f0 --- /dev/null +++ b/tests/plugins/custommsg.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +from pyln.client import Plugin + +plugin = Plugin() + + +@plugin.hook('custommsg') +def on_custommsg(peer_id, message, plugin, **kwargs): + plugin.log("Got a custom message {msg} from peer {peer_id}".format( + msg=message, + peer_id=peer_id + )) + + +plugin.run() diff --git a/tests/test_misc.py b/tests/test_misc.py index e10e7cac0db0..e55c9dddbd46 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -2060,3 +2060,70 @@ def test_waitblockheight(node_factory, executor, bitcoind): bitcoind.generate_block(1) sync_blockheight(bitcoind, [node]) fut2.result(5) + + +@unittest.skipIf(not DEVELOPER, "Needs dev-sendcustommsg") +def test_sendcustommsg(node_factory): + """Check that we can send custommsgs to peers in various states. + + `l2` is the node under test. `l1` has a channel with `l2` and should + therefore be attached to `channeld`. `l4` is just connected, so it should + be attached to `openingd`. `l3` has a channel open, but is disconnected + and we can't send to it. + + """ + plugin = os.path.join(os.path.dirname(__file__), "plugins", "custommsg.py") + opts = {'log-level': 'io', 'plugin': plugin} + l1, l2, l3 = node_factory.line_graph(3, opts=opts) + l4 = node_factory.get_node(options=opts) + l2.connect(l4) + l3.stop() + msg = r'ff' * 32 + serialized = r'04070020' + msg + + # This address doesn't exist so we should get an error when we try sending + # a message to it. + node_id = '02df5ffe895c778e10f7742a6c5b8a0cefbe9465df58b92fadeb883752c8107c8f' + with pytest.raises(RpcError, match=r'No such peer'): + l1.rpc.dev_sendcustommsg(node_id, msg) + + # `l3` is disconnected and we can't send messages to it + assert(not l2.rpc.listpeers(l3.info['id'])['peers'][0]['connected']) + with pytest.raises(RpcError, match=r'Peer is not connected'): + l2.rpc.dev_sendcustommsg(l3.info['id'], msg) + + # We should not be able to send a bogus `ping` message, since it collides + # with a message defined in the spec, and could potentially mess up our + # internal state. + with pytest.raises(RpcError, match=r'Cannot send messages of type 18 .WIRE_PING.'): + l2.rpc.dev_sendcustommsg(l2.info['id'], r'0012') + + # The sendcustommsg RPC call is currently limited to odd-typed messages, + # since they will not result in disconnections or even worse channel + # failures. + with pytest.raises(RpcError, match=r'Cannot send even-typed [0-9]+ custom message'): + l2.rpc.dev_sendcustommsg(l2.info['id'], r'00FE') + + # This should work since the peer is currently owned by `channeld` + l2.rpc.dev_sendcustommsg(l1.info['id'], msg) + l2.daemon.wait_for_log( + r'{peer_id}-{owner}-chan#[0-9]: \[OUT\] {serialized}'.format( + owner='channeld', serialized=serialized, peer_id=l1.info['id'] + ) + ) + l1.daemon.wait_for_log(r'\[IN\] {}'.format(serialized)) + l1.daemon.wait_for_log( + r'Got a custom message {serialized} from peer {peer_id}'.format( + serialized=serialized, peer_id=l2.info['id'])) + + # This should work since the peer is currently owned by `openingd` + l2.rpc.dev_sendcustommsg(l4.info['id'], msg) + l2.daemon.wait_for_log( + r'{peer_id}-{owner}-chan#[0-9]: \[OUT\] {serialized}'.format( + owner='openingd', serialized=serialized, peer_id=l4.info['id'] + ) + ) + l4.daemon.wait_for_log(r'\[IN\] {}'.format(serialized)) + l4.daemon.wait_for_log( + r'Got a custom message {serialized} from peer {peer_id}'.format( + serialized=serialized, peer_id=l2.info['id'])) diff --git a/tools/gen/header_template b/tools/gen/header_template index c7e816d93e28..6175dcd5ae2a 100644 --- a/tools/gen/header_template +++ b/tools/gen/header_template @@ -26,6 +26,15 @@ enum ${enum_set['name']} { ## The 'name' functions for the enums % for enum_set in enum_sets: const char *${enum_set['name']}_name(int e); + +/** + * Determine whether a given message type is defined as a message. + * + * Returns true if the message type is part of the message definitions we have + * generated parsers for, false if it is a custom message that cannot be + * handled internally. + */ +bool ${enum_set['name']}_is_defined(u16 type); % endfor ## Structs for subtypes + tlv messages diff --git a/tools/gen/impl_template b/tools/gen/impl_template index 118d57a263a6..1c4f2e231f50 100644 --- a/tools/gen/impl_template +++ b/tools/gen/impl_template @@ -31,6 +31,18 @@ const char *${enum_set['name']}_name(int e) snprintf(invalidbuf, sizeof(invalidbuf), "INVALID %i", e); return invalidbuf; } + +bool ${enum_set['name']}_is_defined(u16 type) +{ + switch ((enum ${enum_set['name']})type) { + % for msg in enum_set['set']: + case ${msg.enum_name()}:; + % endfor + return true; + } + return false; +} + % endfor ## START PARTIALS ## Subtype and TLV-msg towire_ diff --git a/wallet/test/run-wallet.c b/wallet/test/run-wallet.c index 3e1d48356889..b6f5fcbef58c 100644 --- a/wallet/test/run-wallet.c +++ b/wallet/test/run-wallet.c @@ -111,6 +111,9 @@ bool fromwire_channel_sending_commitsig(const tal_t *ctx UNNEEDED, const void *p /* Generated stub for fromwire_connect_peer_connected */ bool fromwire_connect_peer_connected(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct node_id *id UNNEEDED, struct wireaddr_internal *addr UNNEEDED, struct per_peer_state **pps UNNEEDED, u8 **features UNNEEDED) { fprintf(stderr, "fromwire_connect_peer_connected called!\n"); abort(); } +/* Generated stub for fromwire_custommsg_in */ +bool fromwire_custommsg_in(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, u8 **msg UNNEEDED) +{ fprintf(stderr, "fromwire_custommsg_in called!\n"); abort(); } /* Generated stub for fromwire_gossip_get_channel_peer_reply */ bool fromwire_gossip_get_channel_peer_reply(const tal_t *ctx UNNEEDED, const void *p UNNEEDED, struct node_id **peer_id UNNEEDED) { fprintf(stderr, "fromwire_gossip_get_channel_peer_reply called!\n"); abort(); } @@ -420,6 +423,11 @@ void outpointfilter_remove(struct outpointfilter *of UNNEEDED, bool param(struct command *cmd UNNEEDED, const char *buffer UNNEEDED, const jsmntok_t params[] UNNEEDED, ...) { fprintf(stderr, "param called!\n"); abort(); } +/* Generated stub for param_bin_from_hex */ +struct command_result *param_bin_from_hex(struct command *cmd UNNEEDED, const char *name UNNEEDED, + const char *buffer UNNEEDED, const jsmntok_t *tok UNNEEDED, + u8 **bin UNNEEDED) +{ fprintf(stderr, "param_bin_from_hex called!\n"); abort(); } /* Generated stub for param_bitcoin_address */ struct command_result *param_bitcoin_address(struct command *cmd UNNEEDED, const char *name UNNEEDED, @@ -491,6 +499,9 @@ void payment_store(struct lightningd *ld UNNEEDED, struct wallet_payment *paymen void payment_succeeded(struct lightningd *ld UNNEEDED, struct htlc_out *hout UNNEEDED, const struct preimage *rval UNNEEDED) { fprintf(stderr, "payment_succeeded called!\n"); abort(); } +/* Generated stub for peer_get_owning_subd */ +struct subd *peer_get_owning_subd(struct peer *peer UNNEEDED) +{ fprintf(stderr, "peer_get_owning_subd called!\n"); abort(); } /* Generated stub for peer_memleak_done */ void peer_memleak_done(struct command *cmd UNNEEDED, struct subd *leaker UNNEEDED) { fprintf(stderr, "peer_memleak_done called!\n"); abort(); } @@ -590,6 +601,9 @@ u8 *towire_connectctl_connect_to_peer(const tal_t *ctx UNNEEDED, const struct no /* Generated stub for towire_connectctl_peer_disconnected */ u8 *towire_connectctl_peer_disconnected(const tal_t *ctx UNNEEDED, const struct node_id *id UNNEEDED) { fprintf(stderr, "towire_connectctl_peer_disconnected called!\n"); abort(); } +/* Generated stub for towire_custommsg_out */ +u8 *towire_custommsg_out(const tal_t *ctx UNNEEDED, const u8 *msg UNNEEDED) +{ fprintf(stderr, "towire_custommsg_out called!\n"); abort(); } /* Generated stub for towire_errorfmt */ u8 *towire_errorfmt(const tal_t *ctx UNNEEDED, const struct channel_id *channel UNNEEDED, @@ -629,6 +643,12 @@ struct txowatch *watch_txo(const tal_t *ctx UNNEEDED, size_t input_num UNNEEDED, const struct block *block)) { fprintf(stderr, "watch_txo called!\n"); abort(); } +/* Generated stub for wire_type_is_defined */ +bool wire_type_is_defined(u16 type UNNEEDED) +{ fprintf(stderr, "wire_type_is_defined called!\n"); abort(); } +/* Generated stub for wire_type_name */ +const char *wire_type_name(int e UNNEEDED) +{ fprintf(stderr, "wire_type_name called!\n"); abort(); } /* AUTOGENERATED MOCKS END */ #if DEVELOPER diff --git a/wire/Makefile b/wire/Makefile index 8a0efb011728..92bec190bdc8 100644 --- a/wire/Makefile +++ b/wire/Makefile @@ -10,8 +10,8 @@ WIRE_HEADERS_NOGEN := wire/onion_defs.h \ wire/wire.h \ wire/wire_sync.h \ wire/wire_io.h -WIRE_GEN_HEADERS := wire/gen_peer_wire.h wire/gen_onion_wire.h -WIRE_GEN_SRC := wire/gen_peer_wire.c +WIRE_GEN_HEADERS := wire/gen_peer_wire.h wire/gen_onion_wire.h wire/gen_common_wire.h +WIRE_GEN_SRC := wire/gen_peer_wire.c wire/gen_common_wire.c WIRE_GEN_ONION_SRC := wire/gen_onion_wire.c WIRE_SRC := wire/wire_sync.c \ wire/wire_io.c \ @@ -85,6 +85,13 @@ wire/gen_onion_wire.h: wire/gen_onion_wire_csv $(WIRE_BOLT_DEPS) wire/Makefile wire/gen_onion_wire.c: wire/gen_onion_wire_csv $(WIRE_BOLT_DEPS) wire/Makefile $(BOLT_GEN) -s --expose-tlv-type=tlv_payload --page impl ${@:.c=.h} onion_type < $< > $@ +# Some messages that are common among all daemons +wire/gen_common_wire.h: wire/common_wire_csv $(WIRE_BOLT_DEPS) wire/Makefile + $(BOLT_GEN) -s --page header $@ common_wire_type < $< > $@ + +wire/gen_common_wire.c: wire/common_wire_csv $(WIRE_BOLT_DEPS) wire/Makefile + $(BOLT_GEN) -s --page impl ${@:.c=.h} common_wire_type < $< > $@ + check-source: $(WIRE_SRC:%=check-src-include-order/%) $(WIRE_HEADERS_NOGEN:%=check-hdr-include-order/%) check-source-bolt: $(WIRE_SRC:%=bolt-check/%) $(WIRE_HEADERS_NOGEN:%=bolt-check/%) diff --git a/wire/common_wire_csv b/wire/common_wire_csv new file mode 100644 index 000000000000..7e607c806ccc --- /dev/null +++ b/wire/common_wire_csv @@ -0,0 +1,10 @@ +# A custom message that we got from a peer and don't know how to handle, so we +# forward it to the master for further handling. +msgtype,custommsg_in,1030 +msgdata,custommsg_in,msg_len,u16, +msgdata,custommsg_in,msg,u8,msg_len + +# A custom message that the master tells us to send to the peer. +msgtype,custommsg_out,1031 +msgdata,custommsg_out,msg_len,u16, +msgdata,custommsg_out,msg,u8,msg_len