gossipd: handle wildcard addresses correctly.

If we're given a wildcard address, we can't announce it like that: we need
to try to turn it into a real address (using guess_address).  Then we
use that address.  As a side-effect of this cleanup, we only announce
*any* '--addr' if it's routable.

This fix means that our tests have to force '--announce-addr' because
otherwise localhost isn't routable.

This means that gossipd really controls the addresses now, and breaks
them into two arrays: what we bind to, and what we announce.  That is
now what we return to the master for json_getinfo(), which prints them
as 'bindings' and 'addresses' respectively.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
Rusty Russell 2018-05-07 13:59:22 +09:30 committed by Christian Decker
parent 52917ff6c9
commit af065417e1
13 changed files with 181 additions and 149 deletions

View File

@ -1,6 +1,7 @@
#include <arpa/inet.h>
#include <assert.h>
#include <ccan/build_assert/build_assert.h>
#include <ccan/mem/mem.h>
#include <ccan/tal/str/str.h>
#include <common/type_to_string.h>
#include <common/utils.h>
@ -164,6 +165,18 @@ bool wireaddr_to_ipv6(const struct wireaddr *addr, struct sockaddr_in6 *s6)
return true;
}
bool wireaddr_is_wildcard(const struct wireaddr *addr)
{
switch (addr->type) {
case ADDR_TYPE_IPV6:
case ADDR_TYPE_IPV4:
return memeqzero(addr->addr, addr->addrlen);
case ADDR_TYPE_PADDING:
return false;
}
abort();
}
char *fmt_wireaddr_internal(const tal_t *ctx,
const struct wireaddr_internal *a)
{

View File

@ -75,6 +75,8 @@ void wireaddr_from_ipv6(struct wireaddr *addr,
bool wireaddr_to_ipv4(const struct wireaddr *addr, struct sockaddr_in *s4);
bool wireaddr_to_ipv6(const struct wireaddr *addr, struct sockaddr_in6 *s6);
bool wireaddr_is_wildcard(const struct wireaddr *addr);
enum wireaddr_internal_type {
ADDR_INTERNAL_SOCKNAME,
ADDR_INTERNAL_ALLPROTO,

View File

@ -129,8 +129,13 @@ struct daemon {
u8 alias[33];
u8 rgb[3];
struct wireaddr_internal *wireaddrs;
enum addr_listen_announce *listen_announce;
/* Addresses master told us to use */
struct wireaddr_internal *proposed_wireaddr;
enum addr_listen_announce *proposed_listen_announce;
/* What we actually announce. */
struct wireaddr *announcable;
/* To make sure our node_announcement timestamps increase */
u32 last_announce_timestamp;
@ -566,15 +571,8 @@ static u8 *create_node_announcement(const tal_t *ctx, struct daemon *daemon,
sig = tal(ctx, secp256k1_ecdsa_signature);
memset(sig, 0, sizeof(*sig));
}
for (i = 0; i < tal_count(daemon->wireaddrs); i++) {
if (!(daemon->listen_announce[i] & ADDR_ANNOUNCE))
continue;
/* You can only announce wiretypes! */
if (daemon->wireaddrs[i].itype != ADDR_INTERNAL_WIREADDR)
continue;
towire_wireaddr(&addresses,
&daemon->wireaddrs[i].u.wireaddr);
}
for (i = 0; i < tal_count(daemon->announcable); i++)
towire_wireaddr(&addresses, &daemon->announcable[i]);
announcement =
towire_node_announcement(ctx, sig, features, timestamp,
@ -1607,51 +1605,113 @@ static bool handle_wireaddr_listen(struct daemon *daemon,
"Invalid listener wireaddress type %u", wireaddr->type);
}
static void setup_listeners(struct daemon *daemon)
/* If it's a wildcard, turns it into a real address pointing to internet */
static bool public_address(struct wireaddr *wireaddr)
{
if (wireaddr_is_wildcard(wireaddr)) {
if (!guess_address(wireaddr))
return false;
}
return address_routable(wireaddr);
}
static void add_announcable(struct daemon *daemon, const struct wireaddr *addr)
{
size_t n = tal_count(daemon->announcable);
tal_resize(&daemon->announcable, n+1);
daemon->announcable[n] = *addr;
}
static void add_binding(struct wireaddr_internal **binding,
const struct wireaddr_internal *addr)
{
size_t n = tal_count(*binding);
tal_resize(binding, n+1);
(*binding)[n] = *addr;
}
/* Initializes daemon->announcable array, returns addresses we bound to. */
static struct wireaddr_internal *setup_listeners(const tal_t *ctx,
struct daemon *daemon)
{
struct sockaddr_un addrun;
int fd;
struct wireaddr_internal *binding;
for (size_t i = 0; i < tal_count(daemon->wireaddrs); i++) {
struct wireaddr wa = daemon->wireaddrs[i].u.wireaddr;
binding = tal_arr(ctx, struct wireaddr_internal, 0);
daemon->announcable = tal_arr(daemon, struct wireaddr, 0);
if (!(daemon->listen_announce[i] & ADDR_LISTEN))
for (size_t i = 0; i < tal_count(daemon->proposed_wireaddr); i++) {
struct wireaddr_internal wa = daemon->proposed_wireaddr[i];
if (!(daemon->proposed_listen_announce[i] & ADDR_LISTEN)) {
assert(daemon->proposed_listen_announce[i]
& ADDR_ANNOUNCE);
/* You can only announce wiretypes! */
assert(daemon->proposed_wireaddr[i].itype
== ADDR_INTERNAL_WIREADDR);
add_announcable(daemon, &wa.u.wireaddr);
continue;
}
switch (daemon->wireaddrs[i].itype) {
switch (wa.itype) {
case ADDR_INTERNAL_SOCKNAME:
addrun.sun_family = AF_UNIX;
memcpy(addrun.sun_path, daemon->wireaddrs[i].u.sockname,
memcpy(addrun.sun_path, wa.u.sockname,
sizeof(addrun.sun_path));
fd = make_listen_fd(AF_INET, &addrun, sizeof(addrun),
false);
status_trace("Created socket listener on file %s",
addrun.sun_path);
io_new_listener(daemon, fd, connection_in, daemon);
/* We don't announce socket names */
add_binding(&binding, &wa);
continue;
case ADDR_INTERNAL_ALLPROTO: {
bool ipv6_ok;
memset(wa.addr, 0, sizeof(wa.addr));
wa.type = ADDR_TYPE_IPV6;
wa.addrlen = 16;
wa.itype = ADDR_INTERNAL_WIREADDR;
wa.u.wireaddr.port = wa.u.port;
memset(wa.u.wireaddr.addr, 0,
sizeof(wa.u.wireaddr.addr));
ipv6_ok = handle_wireaddr_listen(daemon, &wa, true);
wa.type = ADDR_TYPE_IPV4;
wa.addrlen = 4;
/* Try both IPv6 and IPv4. */
wa.u.wireaddr.type = ADDR_TYPE_IPV6;
wa.u.wireaddr.addrlen = 16;
ipv6_ok = handle_wireaddr_listen(daemon, &wa.u.wireaddr,
true);
if (ipv6_ok) {
add_binding(&binding, &wa);
if (public_address(&wa.u.wireaddr))
add_announcable(daemon, &wa.u.wireaddr);
}
wa.u.wireaddr.type = ADDR_TYPE_IPV4;
wa.u.wireaddr.addrlen = 4;
/* OK if this fails, as long as one succeeds! */
handle_wireaddr_listen(daemon, &wa, ipv6_ok);
if (handle_wireaddr_listen(daemon, &wa.u.wireaddr,
ipv6_ok)) {
add_binding(&binding, &wa);
if (public_address(&wa.u.wireaddr))
add_announcable(daemon, &wa.u.wireaddr);
}
continue;
}
case ADDR_INTERNAL_WIREADDR:
handle_wireaddr_listen(daemon, &wa, false);
handle_wireaddr_listen(daemon, &wa.u.wireaddr, false);
add_binding(&binding, &wa);
if (public_address(&wa.u.wireaddr))
add_announcable(daemon, &wa.u.wireaddr);
continue;
}
/* Shouldn't happen. */
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Invalid listener address type %u",
daemon->wireaddrs[i].itype);
daemon->proposed_wireaddr[i].itype);
}
return binding;
}
/* Parse an incoming gossip init message and assign config variables
@ -1667,8 +1727,8 @@ static struct io_plan *gossip_init(struct daemon_conn *master,
if (!fromwire_gossipctl_init(
daemon, msg, &daemon->broadcast_interval, &chain_hash,
&daemon->id, &daemon->globalfeatures,
&daemon->localfeatures, &daemon->wireaddrs,
&daemon->listen_announce, daemon->rgb,
&daemon->localfeatures, &daemon->proposed_wireaddr,
&daemon->proposed_listen_announce, daemon->rgb,
daemon->alias, &update_channel_interval, &daemon->reconnect)) {
master_badmsg(WIRE_GOSSIPCTL_INIT, msg);
}
@ -1691,18 +1751,21 @@ static struct io_plan *gossip_activate(struct daemon_conn *master,
const u8 *msg)
{
bool listen;
struct wireaddr_internal *binding;
if (!fromwire_gossipctl_activate(msg, &listen))
master_badmsg(WIRE_GOSSIPCTL_ACTIVATE, msg);
if (listen)
setup_listeners(daemon);
binding = setup_listeners(tmpctx, daemon);
else
binding = NULL;
/* OK, we're ready! */
daemon_conn_send(&daemon->master,
take(towire_gossipctl_activate_reply(NULL,
daemon->wireaddrs,
daemon->listen_announce)));
binding,
daemon->announcable)));
return daemon_conn_read_next(master->conn, master);
}

View File

@ -24,11 +24,12 @@ gossipctl_activate,3025
# Do we listen?
gossipctl_activate,,listen,bool
# Gossipd->master, I am ready, here are the final addresses.
# Gossipd->master, I am ready, here's the addresses I bound, can announce.
gossipctl_activate_reply,3125
gossipctl_activate_reply,,num_wireaddrs,u16
gossipctl_activate_reply,,wireaddrs,num_wireaddrs*struct wireaddr_internal
gossipctl_activate_reply,,listen_announce,num_wireaddrs*enum addr_listen_announce
gossipctl_activate_reply,,num_bindings,u16
gossipctl_activate_reply,,bindings,num_bindings*struct wireaddr_internal
gossipctl_activate_reply,,num_announcable,u16
gossipctl_activate_reply,,announcable,num_announcable*struct wireaddr
# Master -> gossipd: Optional hint for where to find peer.
gossipctl_peer_addrhint,3014

1 #include <common/cryptomsg.h>
24 # Gossipd->master, I am ready, here are the final addresses. # Gossipd->master, I am ready, here's the addresses I bound, can announce.
25 gossipctl_activate_reply,3125
26 gossipctl_activate_reply,,num_wireaddrs,u16 gossipctl_activate_reply,,num_bindings,u16
27 gossipctl_activate_reply,,wireaddrs,num_wireaddrs*struct wireaddr_internal gossipctl_activate_reply,,bindings,num_bindings*struct wireaddr_internal
28 gossipctl_activate_reply,,listen_announce,num_wireaddrs*enum addr_listen_announce gossipctl_activate_reply,,num_announcable,u16
29 # Master -> gossipd: Optional hint for where to find peer. gossipctl_activate_reply,,announcable,num_announcable*struct wireaddr
30 gossipctl_peer_addrhint,3014 # Master -> gossipd: Optional hint for where to find peer.
31 gossipctl_peer_addrhint,,id,struct pubkey gossipctl_peer_addrhint,3014
32 gossipctl_peer_addrhint,,id,struct pubkey
33 gossipctl_peer_addrhint,,addr,struct wireaddr_internal
34 # Master -> gossipd: connect to a peer.
35 gossipctl_connect_to_peer,3001

View File

@ -201,48 +201,39 @@ static bool IsRoutable(const struct wireaddr *addr)
* then query address. */
/* Returns 0 if protocol completely unsupported, ADDR_LISTEN if we
* can't reach addr, ADDR_LISTEN_AND_ANNOUNCE if we can (and fill saddr). */
static enum addr_listen_announce get_local_sockname(int af, void *saddr,
socklen_t saddrlen)
static bool get_local_sockname(int af, void *saddr, socklen_t saddrlen)
{
int fd = socket(af, SOCK_DGRAM, 0);
if (fd < 0) {
status_trace("Failed to create %u socket: %s",
af, strerror(errno));
return 0;
return false;
}
if (connect(fd, saddr, saddrlen) != 0) {
status_trace("Failed to connect %u socket: %s",
af, strerror(errno));
close(fd);
return ADDR_LISTEN;
return false;
}
if (getsockname(fd, saddr, &saddrlen) != 0) {
status_trace("Failed to get %u socket name: %s",
af, strerror(errno));
close(fd);
return ADDR_LISTEN;
return false;
}
close(fd);
return ADDR_LISTEN_AND_ANNOUNCE;
return true;
}
/* Return 0 if not available, or whether it's listenable-only or announceable.
* If it's listenable only, will set wireaddr to all-zero address for universal
* binding. */
static enum addr_listen_announce guess_one_address(struct wireaddr *addr,
u16 portnum,
enum wire_addr_type type)
bool guess_address(struct wireaddr *addr)
{
enum addr_listen_announce ret;
addr->type = type;
addr->port = portnum;
bool ret;
/* We point to Google nameservers, works unless you're inside Google :) */
switch (type) {
switch (addr->type) {
case ADDR_TYPE_IPV4: {
struct sockaddr_in sin;
sin.sin_port = htons(53);
@ -252,7 +243,7 @@ static enum addr_listen_announce guess_one_address(struct wireaddr *addr,
ret = get_local_sockname(AF_INET, &sin, sizeof(sin));
addr->addrlen = sizeof(sin.sin_addr);
memcpy(addr->addr, &sin.sin_addr, addr->addrlen);
break;
return ret;
}
case ADDR_TYPE_IPV6: {
struct sockaddr_in6 sin6;
@ -266,59 +257,15 @@ static enum addr_listen_announce guess_one_address(struct wireaddr *addr,
ret = get_local_sockname(AF_INET6, &sin6, sizeof(sin6));
addr->addrlen = sizeof(sin6.sin6_addr);
memcpy(addr->addr, &sin6.sin6_addr, addr->addrlen);
break;
return ret;
}
case ADDR_TYPE_PADDING:
status_trace("Padding address, ignoring");
return 0;
break;
}
abort();
}
if (ret == 0)
return ret;
/* If we can reach it, but resulting address is unroutable, listen only */
if (ret == ADDR_LISTEN_AND_ANNOUNCE && !IsRoutable(addr)) {
status_trace("Address %s is not routable",
type_to_string(tmpctx, struct wireaddr, addr));
ret = ADDR_LISTEN;
}
if (ret == ADDR_LISTEN) {
/* This corresponds to INADDR_ANY or in6addr_any */
memset(addr->addr, 0, addr->addrlen);
} else {
status_trace("Public address %s",
type_to_string(tmpctx, struct wireaddr, addr));
}
return ret;
}
void guess_addresses(struct wireaddr_internal **wireaddrs,
enum addr_listen_announce **listen_announce,
u16 portnum)
bool address_routable(const struct wireaddr *wireaddr)
{
size_t n = tal_count(*wireaddrs);
status_trace("Trying to guess public addresses...");
/* We allocate an extra, then remove if it's not needed. */
tal_resize(wireaddrs, n+1);
tal_resize(listen_announce, n+1);
(*wireaddrs)[n].itype = ADDR_INTERNAL_WIREADDR;
/* We do IPv6 first: on Linux, that binds to IPv4 too. */
(*listen_announce)[n] = guess_one_address(&(*wireaddrs)[n].u.wireaddr,
portnum, ADDR_TYPE_IPV6);
if ((*listen_announce)[n] != 0) {
n++;
tal_resize(wireaddrs, n+1);
tal_resize(listen_announce, n+1);
(*wireaddrs)[n].itype = ADDR_INTERNAL_WIREADDR;
}
(*listen_announce)[n] = guess_one_address(&(*wireaddrs)[n].u.wireaddr,
portnum, ADDR_TYPE_IPV4);
if ((*listen_announce)[n] == 0) {
tal_resize(wireaddrs, n);
tal_resize(listen_announce, n);
}
return IsRoutable(wireaddr);
}

View File

@ -4,8 +4,10 @@
#include <ccan/short_types/short_types.h>
#include <common/wireaddr.h>
void guess_addresses(struct wireaddr_internal **wireaddrs,
enum addr_listen_announce **listen_announce,
u16 portnum);
/* Address is a wildcard: try to guess what it looks like to outside world */
bool guess_address(struct wireaddr *wireaddr);
/* Is this address public? */
bool address_routable(const struct wireaddr *wireaddr);
#endif /* LIGHTNING_GOSSIPD_NETADDRESS_H */

View File

@ -183,8 +183,8 @@ void gossip_init(struct lightningd *ld)
u8 *msg;
int hsmfd;
u64 capabilities = HSM_CAP_ECDH | HSM_CAP_SIGN_GOSSIP;
struct wireaddr_internal *wireaddrs = ld->wireaddrs;
enum addr_listen_announce *listen_announce = ld->listen_announce;
struct wireaddr_internal *wireaddrs = ld->proposed_wireaddr;
enum addr_listen_announce *listen_announce = ld->proposed_listen_announce;
msg = towire_hsm_client_hsmfd(tmpctx, &ld->id, capabilities);
if (!wire_sync_write(ld->hsm_fd, msg))
@ -230,12 +230,9 @@ static void gossip_activate_done(struct subd *gossip UNUSED,
{
struct lightningd *ld = gossip->ld;
/* Reply gives us the actual wireaddrs we're using */
tal_free(ld->wireaddrs);
tal_free(ld->listen_announce);
if (!fromwire_gossipctl_activate_reply(gossip->ld, reply,
&ld->wireaddrs,
&ld->listen_announce))
&ld->binding,
&ld->announcable))
fatal("Bad gossipctl_activate_reply: %s",
tal_hex(reply, reply));

View File

@ -150,31 +150,22 @@ static void json_getinfo(struct command *cmd,
json_object_start(response, NULL);
json_add_pubkey(response, "id", &cmd->ld->id);
if (cmd->ld->listen) {
bool have_listen_no_announce = false;
if (deprecated_apis)
json_add_num(response, "port", cmd->ld->portnum);
/* These are the addresses we're announcing */
json_array_start(response, "address");
for (size_t i = 0; i < tal_count(cmd->ld->wireaddrs); i++) {
if (cmd->ld->listen_announce[i] & ADDR_ANNOUNCE)
json_add_address_internal(response, NULL,
cmd->ld->wireaddrs+i);
else
have_listen_no_announce = true;
}
for (size_t i = 0; i < tal_count(cmd->ld->announcable); i++)
json_add_address(response, NULL, cmd->ld->announcable+i);
json_array_end(response);
if (have_listen_no_announce) {
json_array_start(response, "listen-only");
for (size_t i = 0; i < tal_count(cmd->ld->wireaddrs); i++) {
if (cmd->ld->listen_announce[i] == ADDR_LISTEN)
/* This is what we're actually bound to. */
json_array_start(response, "binding");
for (size_t i = 0; i < tal_count(cmd->ld->binding); i++)
json_add_address_internal(response, NULL,
cmd->ld->wireaddrs+i);
}
cmd->ld->binding+i);
json_array_end(response);
}
}
json_add_string(response, "version", version());
json_add_num(response, "blockheight", get_block_height(cmd->ld->topology));
json_add_string(response, "network", get_chainparams(cmd->ld)->network_name);

View File

@ -69,8 +69,8 @@ static struct lightningd *new_lightningd(const tal_t *ctx)
list_head_init(&ld->waitsendpay_commands);
list_head_init(&ld->sendpay_commands);
list_head_init(&ld->close_commands);
ld->wireaddrs = tal_arr(ld, struct wireaddr_internal, 0);
ld->listen_announce = tal_arr(ld, enum addr_listen_announce, 0);
ld->proposed_wireaddr = tal_arr(ld, struct wireaddr_internal, 0);
ld->proposed_listen_announce = tal_arr(ld, enum addr_listen_announce, 0);
ld->portnum = DEFAULT_PORT;
ld->listen = true;
ld->autolisten = true;

View File

@ -120,10 +120,14 @@ struct lightningd {
/* Do we want to guess addresses to listen and announce? */
bool autolisten;
/* Addresses to bind/announce to the network (tal_count()) */
struct wireaddr_internal *wireaddrs;
/* And the bitset for each, whether to listen, announce or both */
enum addr_listen_announce *listen_announce;
/* Setup: Addresses to bind/announce to the network (tal_count()) */
struct wireaddr_internal *proposed_wireaddr;
/* Setup: And the bitset for each, whether to listen, announce or both */
enum addr_listen_announce *proposed_listen_announce;
/* Actual bindings and announcables from gossipd */
struct wireaddr_internal *binding;
struct wireaddr *announcable;
/* Bearer of all my secrets. */
int hsm_fd;

View File

@ -149,16 +149,16 @@ static char *opt_add_addr_withtype(const char *arg,
struct lightningd *ld,
enum addr_listen_announce ala)
{
size_t n = tal_count(ld->wireaddrs);
size_t n = tal_count(ld->proposed_wireaddr);
char const *err_msg;
assert(arg != NULL);
tal_resize(&ld->wireaddrs, n+1);
tal_resize(&ld->listen_announce, n+1);
ld->listen_announce[n] = ala;
tal_resize(&ld->proposed_wireaddr, n+1);
tal_resize(&ld->proposed_listen_announce, n+1);
ld->proposed_listen_announce[n] = ala;
if (!parse_wireaddr_internal(arg, &ld->wireaddrs[n], ld->portnum,
if (!parse_wireaddr_internal(arg, &ld->proposed_wireaddr[n], ld->portnum,
true, &err_msg)) {
return tal_fmt(NULL, "Unable to parse address '%s': %s", arg, err_msg);
}
@ -918,17 +918,20 @@ static void add_config(struct lightningd *ld,
/* Covered by opt_add_addr below */
} else if (opt->cb_arg == (void *)opt_add_addr) {
json_add_opt_addrs(response, name0,
ld->wireaddrs, ld->listen_announce,
ld->proposed_wireaddr,
ld->proposed_listen_announce,
ADDR_LISTEN_AND_ANNOUNCE);
return;
} else if (opt->cb_arg == (void *)opt_add_bind_addr) {
json_add_opt_addrs(response, name0,
ld->wireaddrs, ld->listen_announce,
ld->proposed_wireaddr,
ld->proposed_listen_announce,
ADDR_LISTEN);
return;
} else if (opt->cb_arg == (void *)opt_add_announce_addr) {
json_add_opt_addrs(response, name0,
ld->wireaddrs, ld->listen_announce,
ld->proposed_wireaddr,
ld->proposed_listen_announce,
ADDR_ANNOUNCE);
return;
#if DEVELOPER

View File

@ -4283,10 +4283,17 @@ class LightningDTests(BaseLightningDTests):
def test_address(self):
l1 = self.node_factory.get_node()
assert len(l1.rpc.getinfo()['address']) == 1
assert l1.rpc.getinfo()['address'][0]['type'] == 'ipv4'
assert l1.rpc.getinfo()['address'][0]['address'] == '127.0.0.1'
assert int(l1.rpc.getinfo()['address'][0]['port']) == l1.port
addr = l1.rpc.getinfo()['address']
assert len(addr) == 1
assert addr[0]['type'] == 'ipv4'
assert addr[0]['address'] == '127.0.0.1'
assert int(addr[0]['port']) == l1.port
bind = l1.rpc.getinfo()['binding']
assert len(bind) == 1
assert bind[0]['type'] == 'ipv4'
assert bind[0]['address'] == '127.0.0.1'
assert int(bind[0]['port']) == l1.port
def test_listconfigs(self):
l1 = self.node_factory.get_node()

View File

@ -261,7 +261,9 @@ class LightningD(TailableProc):
opts = {
'bitcoin-datadir': bitcoin_dir,
'lightning-dir': lightning_dir,
'addr': '127.0.0.1:{}'.format(port),
'bind-addr': '127.0.0.1:{}'.format(port),
# lightningd won't announce non-routable addresses by default.
'announce-addr': '127.0.0.1:{}'.format(port),
'allow-deprecated-apis': 'false',
'override-fee-rates': '15000/7500/1000',
'network': 'regtest',
@ -370,7 +372,7 @@ class LightningNode(object):
def start(self):
self.daemon.start()
# This shortcut is sufficient for our simple tests.
self.port = self.rpc.getinfo()['address'][0]['port']
self.port = self.rpc.getinfo()['binding'][0]['port']
def stop(self, timeout=10):
""" Attempt to do a clean shutdown, but kill if it hangs