From 01e8a523e9a23f5d64166696f7629a13947672e3 Mon Sep 17 00:00:00 2001 From: Michael Schmoock Date: Fri, 1 Oct 2021 13:47:29 +0200 Subject: [PATCH] bolt7: allow announcement of ADDR_TYPE_DNS --- common/json_helpers.c | 4 +++ common/wireaddr.c | 12 +++++++ common/wireaddr.h | 11 +++++-- connectd/connectd.c | 6 ++++ connectd/netaddress.c | 1 + devtools/gossipwith.c | 3 ++ doc/lightning-getinfo.7.md | 6 ++-- doc/lightning-listnodes.7.md | 12 +++---- doc/schemas/getinfo.schema.json | 2 ++ doc/schemas/listnodes.schema.json | 2 ++ lightningd/options.c | 53 +++++++++++++++++++++---------- tests/test_gossip.py | 34 ++++++++++++++++---- 12 files changed, 111 insertions(+), 35 deletions(-) diff --git a/common/json_helpers.c b/common/json_helpers.c index 44efdffbd..bcde33a61 100644 --- a/common/json_helpers.c +++ b/common/json_helpers.c @@ -281,6 +281,10 @@ void json_add_address(struct json_stream *response, const char *fieldname, json_add_string(response, "type", "torv3"); json_add_string(response, "address", fmt_wireaddr_without_port(tmpctx, addr)); json_add_num(response, "port", addr->port); + } else if (addr->type == ADDR_TYPE_DNS) { + json_add_string(response, "type", "dns"); + json_add_string(response, "address", fmt_wireaddr_without_port(tmpctx, addr)); + json_add_num(response, "port", addr->port); } else if (addr->type == ADDR_TYPE_WEBSOCKET) { json_add_string(response, "type", "websocket"); json_add_num(response, "port", addr->port); diff --git a/common/wireaddr.c b/common/wireaddr.c index f9afbc48e..8130fee5d 100644 --- a/common/wireaddr.c +++ b/common/wireaddr.c @@ -37,6 +37,11 @@ bool fromwire_wireaddr(const u8 **cursor, size_t *max, struct wireaddr *addr) case ADDR_TYPE_TOR_V3: addr->addrlen = TOR_V3_ADDRLEN; break; + case ADDR_TYPE_DNS: + addr->addrlen = fromwire_u8(cursor, max); + memset(&addr->addr, 0, sizeof(addr->addr)); + addr->addr[addr->addrlen] = 0; + break; case ADDR_TYPE_WEBSOCKET: addr->addrlen = 0; break; @@ -52,6 +57,8 @@ bool fromwire_wireaddr(const u8 **cursor, size_t *max, struct wireaddr *addr) void towire_wireaddr(u8 **pptr, const struct wireaddr *addr) { towire_u8(pptr, addr->type); + if (addr->type == ADDR_TYPE_DNS) + towire_u8(pptr, addr->addrlen); towire(pptr, addr->addr, addr->addrlen); towire_u16(pptr, addr->port); } @@ -211,6 +218,7 @@ bool wireaddr_is_wildcard(const struct wireaddr *addr) return memeqzero(addr->addr, addr->addrlen); case ADDR_TYPE_TOR_V2_REMOVED: case ADDR_TYPE_TOR_V3: + case ADDR_TYPE_DNS: case ADDR_TYPE_WEBSOCKET: return false; } @@ -259,6 +267,8 @@ char *fmt_wireaddr_without_port(const tal_t * ctx, const struct wireaddr *a) case ADDR_TYPE_TOR_V3: return tal_fmt(ctx, "%s.onion", b32_encode(tmpctx, a->addr, a->addrlen)); + case ADDR_TYPE_DNS: + return tal_fmt(ctx, "%s", a->addr); case ADDR_TYPE_WEBSOCKET: return tal_strdup(ctx, "websocket"); } @@ -789,6 +799,7 @@ struct addrinfo *wireaddr_to_addrinfo(const tal_t *ctx, return ai; case ADDR_TYPE_TOR_V2_REMOVED: case ADDR_TYPE_TOR_V3: + case ADDR_TYPE_DNS: case ADDR_TYPE_WEBSOCKET: break; } @@ -841,6 +852,7 @@ bool all_tor_addresses(const struct wireaddr_internal *wireaddr) switch (wireaddr[i].u.wireaddr.type) { case ADDR_TYPE_IPV4: case ADDR_TYPE_IPV6: + case ADDR_TYPE_DNS: return false; case ADDR_TYPE_TOR_V2_REMOVED: case ADDR_TYPE_TOR_V3: diff --git a/common/wireaddr.h b/common/wireaddr.h index e9dc0bf8e..8b8b79904 100644 --- a/common/wireaddr.h +++ b/common/wireaddr.h @@ -37,13 +37,18 @@ struct sockaddr_un; * where `checksum = sha3(".onion checksum" | pubkey || version)[:2]` */ +/* BOLT-hostnames #7: + * * `5`: DNS hostname; data = `[byte:len][len*byte:hostname][u16:port]` (length up to 258) + */ + /* BOLT-websockets #7: * * `6`: WebSocket port; data = `[2:port]` (length 2) */ #define TOR_V2_ADDRLEN 10 #define TOR_V3_ADDRLEN 35 -#define LARGEST_ADDRLEN TOR_V3_ADDRLEN +#define DNS_ADDRLEN 255 +#define LARGEST_ADDRLEN DNS_ADDRLEN #define TOR_V3_BLOBLEN 64 #define STATIC_TOR_MAGIC_STRING "gen-default-toraddress" @@ -52,10 +57,10 @@ enum wire_addr_type { ADDR_TYPE_IPV6 = 2, ADDR_TYPE_TOR_V2_REMOVED = 3, ADDR_TYPE_TOR_V3 = 4, - ADDR_TYPE_WEBSOCKET = 6, + ADDR_TYPE_DNS = 5, + ADDR_TYPE_WEBSOCKET = 6 }; -/* Structure now fit for tor support */ struct wireaddr { enum wire_addr_type type; u8 addrlen; diff --git a/connectd/connectd.c b/connectd/connectd.c index 7cd457d75..b92058f2c 100644 --- a/connectd/connectd.c +++ b/connectd/connectd.c @@ -975,6 +975,9 @@ static void try_connect_one_addr(struct connecting *connect) case ADDR_TYPE_IPV6: af = AF_INET6; break; + case ADDR_TYPE_DNS: + // TODO: resolve with getaddrinfo and set af + break; case ADDR_TYPE_WEBSOCKET: af = -1; break; @@ -1159,6 +1162,7 @@ static bool handle_wireaddr_listen(struct daemon *daemon, case ADDR_TYPE_WEBSOCKET: case ADDR_TYPE_TOR_V2_REMOVED: case ADDR_TYPE_TOR_V3: + case ADDR_TYPE_DNS: break; } status_failed(STATUS_FAIL_INTERNAL_ERROR, @@ -1614,6 +1618,8 @@ static void add_seed_addrs(struct wireaddr_internal **addrs, NULL, broken_reply, NULL); if (new_addrs) { for (size_t j = 0; j < tal_count(new_addrs); j++) { + if (new_addrs[j].type == ADDR_TYPE_DNS) + continue; struct wireaddr_internal a; a.itype = ADDR_INTERNAL_WIREADDR; a.u.wireaddr = new_addrs[j]; diff --git a/connectd/netaddress.c b/connectd/netaddress.c index dff03e92d..5f9fb57fd 100644 --- a/connectd/netaddress.c +++ b/connectd/netaddress.c @@ -258,6 +258,7 @@ bool guess_address(struct wireaddr *addr) } case ADDR_TYPE_TOR_V2_REMOVED: case ADDR_TYPE_TOR_V3: + case ADDR_TYPE_DNS: case ADDR_TYPE_WEBSOCKET: status_broken("Cannot guess address type %u", addr->type); break; diff --git a/devtools/gossipwith.c b/devtools/gossipwith.c index 97287103d..f356f0008 100644 --- a/devtools/gossipwith.c +++ b/devtools/gossipwith.c @@ -324,6 +324,9 @@ int main(int argc, char *argv[]) case ADDR_TYPE_WEBSOCKET: opt_usage_exit_fail("Don't support websockets"); break; + case ADDR_TYPE_DNS: + opt_usage_exit_fail("Don't support DNS"); + break; case ADDR_TYPE_IPV4: af = AF_INET; break; diff --git a/doc/lightning-getinfo.7.md b/doc/lightning-getinfo.7.md index 40e1f98d0..a3d4b5b11 100644 --- a/doc/lightning-getinfo.7.md +++ b/doc/lightning-getinfo.7.md @@ -40,10 +40,10 @@ On success, an object is returned, containing: - **network** (string): represents the type of network on the node are working (e.g: `bitcoin`, `testnet`, or `regtest`) - **fees_collected_msat** (msat): Total routing fees collected by this node - **address** (array of objects, optional): The addresses we announce to the world: - - **type** (string): Type of connection (one of "ipv4", "ipv6", "torv2", "torv3", "websocket") + - **type** (string): Type of connection (one of "dns", "ipv4", "ipv6", "torv2", "torv3", "websocket") - **port** (u16): port number - If **type** is "ipv4", "ipv6", "torv2" or "torv3": + If **type** is "dns", "ipv4", "ipv6", "torv2" or "torv3": - **address** (string): address in expected format for **type** - **binding** (array of objects, optional): The addresses we are listening on: - **type** (string): Type of connection (one of "local socket", "ipv4", "ipv6", "torv2", "torv3") @@ -117,4 +117,4 @@ RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:8374064ca0f95ab0c20d3edaf7f3742316af98f4d1e0e8de88922524f1ea3ce5) +[comment]: # ( SHA256STAMP:90a3bacb6cb4456119afee8e60677c29bf5f46c4cd950e660a9f9c8e0433b473) diff --git a/doc/lightning-listnodes.7.md b/doc/lightning-listnodes.7.md index 39d184904..3f36ce07f 100644 --- a/doc/lightning-listnodes.7.md +++ b/doc/lightning-listnodes.7.md @@ -36,10 +36,10 @@ If **last_timestamp** is present: - **color** (hex): The favorite RGB color this node advertized (always 6 characters) - **features** (hex): BOLT #9 features bitmap this node advertized - **addresses** (array of objects): The addresses this node advertized: - - **type** (string): Type of connection (one of "ipv4", "ipv6", "torv2", "torv3", "websocket") + - **type** (string): Type of connection (one of "dns", "ipv4", "ipv6", "torv2", "torv3", "websocket") - **port** (u16): port number - If **type** is "ipv4", "ipv6", "torv2" or "torv3": + If **type** is "dns", "ipv4", "ipv6", "torv2" or "torv3": - **address** (string): address in expected format for **type** If **option_will_fund** is present: @@ -52,9 +52,9 @@ If **option_will_fund** is present: - **compact_lease** (hex): the lease as represented in the node_announcement [comment]: # (GENERATE-FROM-SCHEMA-END) - + On failure, one of the following error codes may be returned: - + - -32602: Error in given parameters. EXAMPLE JSON RESPONSE @@ -89,10 +89,10 @@ Vincenzo Palazzo <> wrote the initial version o SEE ALSO -------- -FIXME: +FIXME: RESOURCES --------- Main web site: -[comment]: # ( SHA256STAMP:4a5cfb1cf3d7fd77e49d6e7e369a9a6d374345b011d7db2fa9b4062156869ca4) +[comment]: # ( SHA256STAMP:85400c9c1741943e2e02935b4f14fd187a7db6056410e42adec07ef3c6772f5f) diff --git a/doc/schemas/getinfo.schema.json b/doc/schemas/getinfo.schema.json index 4e0eda46a..fc07c5de9 100644 --- a/doc/schemas/getinfo.schema.json +++ b/doc/schemas/getinfo.schema.json @@ -86,6 +86,7 @@ "type": { "type": "string", "enum": [ + "dns", "ipv4", "ipv6", "torv2", @@ -104,6 +105,7 @@ "type": { "type": "string", "enum": [ + "dns", "ipv4", "ipv6", "torv2", diff --git a/doc/schemas/listnodes.schema.json b/doc/schemas/listnodes.schema.json index d88250f66..42637bff6 100644 --- a/doc/schemas/listnodes.schema.json +++ b/doc/schemas/listnodes.schema.json @@ -74,6 +74,7 @@ "type": { "type": "string", "enum": [ + "dns", "ipv4", "ipv6", "torv2", @@ -92,6 +93,7 @@ "type": { "type": "string", "enum": [ + "dns", "ipv4", "ipv6", "torv2", diff --git a/lightningd/options.c b/lightningd/options.c index e186b6e1c..8e903cfb3 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -189,31 +189,52 @@ static char *opt_add_addr_withtype(const char *arg, char const *err_msg; struct wireaddr_internal wi; bool dns_ok; + char *address; + u16 port; assert(arg != NULL); dns_ok = !ld->always_use_proxy && ld->config.use_dns; - if (!parse_wireaddr_internal(arg, &wi, - ld->portnum, - wildcard_ok, dns_ok, false, - deprecated_apis, &err_msg)) { - return tal_fmt(NULL, "Unable to parse address '%s': %s", arg, err_msg); + if (!separate_address_and_port(tmpctx, arg, &address, &port)) + return tal_fmt(NULL, "Unable to parse address:port '%s'", arg); + + if (is_ipaddr(address) || is_toraddr(address) || ala != ADDR_ANNOUNCE) { + if (!parse_wireaddr_internal(arg, &wi, ld->portnum, + wildcard_ok, dns_ok, false, + deprecated_apis, &err_msg)) { + return tal_fmt(NULL, "Unable to parse address '%s': %s", arg, err_msg); + } + + /* Sanity check for exact duplicates. */ + for (size_t i = 0; i < tal_count(ld->proposed_wireaddr); i++) { + /* Only compare announce vs announce and bind vs bind */ + if ((ld->proposed_listen_announce[i] & ala) == 0) + continue; + + if (wireaddr_internal_eq(&ld->proposed_wireaddr[i], &wi)) + return tal_fmt(NULL, "Duplicate %s address %s", + ala & ADDR_ANNOUNCE ? "announce" : "listen", + type_to_string(tmpctx, struct wireaddr_internal, &wi)); + } + + tal_arr_expand(&ld->proposed_listen_announce, ala); + tal_arr_expand(&ld->proposed_wireaddr, wi); } - /* Sanity check for exact duplicates. */ - for (size_t i = 0; i < tal_count(ld->proposed_wireaddr); i++) { - /* Only compare announce vs announce and bind vs bind */ - if ((ld->proposed_listen_announce[i] & ala) == 0) - continue; + /* Add ADDR_TYPE_DNS to announce DNS hostnames */ + if (is_dnsaddr(address) && ala & ADDR_ANNOUNCE) { + memset(&wi, 0, sizeof(wi)); + wi.itype = ADDR_INTERNAL_WIREADDR; + wi.u.wireaddr.type = ADDR_TYPE_DNS; + wi.u.wireaddr.addrlen = strlen(address); + strncpy((char * restrict)&wi.u.wireaddr.addr, + address, sizeof(wi.u.wireaddr.addr) - 1); + wi.u.wireaddr.port = port; - if (wireaddr_internal_eq(&ld->proposed_wireaddr[i], &wi)) - return tal_fmt(NULL, "Duplicate %s address %s", - ala & ADDR_ANNOUNCE ? "announce" : "listen", - type_to_string(tmpctx, struct wireaddr_internal, &wi)); + tal_arr_expand(&ld->proposed_listen_announce, ADDR_ANNOUNCE); + tal_arr_expand(&ld->proposed_wireaddr, wi); } - tal_arr_expand(&ld->proposed_listen_announce, ala); - tal_arr_expand(&ld->proposed_wireaddr, wi); return NULL; } diff --git a/tests/test_gossip.py b/tests/test_gossip.py index 2a4bc7ca3..785eaeaaa 100644 --- a/tests/test_gossip.py +++ b/tests/test_gossip.py @@ -110,9 +110,11 @@ def test_announce_address(node_factory, bitcoind): """Make sure our announcements are well formed.""" # We do not allow announcement of duplicates. - opts = {'announce-addr': + opts = {'disable-dns': None, 'announce-addr': ['4acth47i6kxnvkewtm6q7ib2s3ufpo5sqbsnzjpbi7utijcltosqemad.onion', '1.2.3.4:1234', + 'localhost:1235', + 'example.com:1236', '::'], 'log-level': 'io', 'dev-allow-localhost': None} @@ -126,12 +128,30 @@ def test_announce_address(node_factory, bitcoind): l2.wait_channel_active(scid) # We should see it send node announce with all addresses (257 = 0x0101) - # local ephemeral port is masked out. - l1.daemon.wait_for_log(r"\[OUT\] 0101.*47" - "010102030404d2" - "017f000001...." - "02000000000000000000000000000000002607" - "04e00533f3e8f2aedaa8969b3d0fa03a96e857bbb28064dca5e147e934244b9ba50230032607") + # Note: local ephemeral port is masked out. + # Note: Since we `disable-dns` it should not announce a resolved IPv4 + # or IPv6 address for example.com + # + # Also expect the address descriptor types to be sorted! + # BOLT #7: + # - MUST place address descriptors in ascending order. + l1.daemon.wait_for_log(r"\[OUT\] 0101.*0063" + "010102030404d2" # IPv4 01 1.2.3.4:1234 + "017f000001...." # IPv4 01 127.0.0.1:wxyz + "02000000000000000000000000000000002607" # IPv6 02 :::9735 + "04e00533f3e8f2aedaa8969b3d0fa03a96e857bbb28064dca5e147e934244b9ba50230032607" # TORv3 04 + "05096c6f63616c686f737404d3" # DNS 05 len localhost:1235 + "050b6578616d706c652e636f6d04d4") # DNS 05 len example.com:1236 + + # Check other node can parse these + addresses = l2.rpc.listnodes(l1.info['id'])['nodes'][0]['addresses'] + addresses_dns = [address for address in addresses if address['type'] == 'dns'] + assert len(addresses) == 6 + assert len(addresses_dns) == 2 + assert addresses_dns[0]['address'] == 'localhost' + assert addresses_dns[0]['port'] == 1235 + assert addresses_dns[1]['address'] == 'example.com' + assert addresses_dns[1]['port'] == 1236 @pytest.mark.developer("needs DEVELOPER=1")