More flexible address wildcards, only add wildcard if nothing else.

1. Add special option where an empty host means 'wildcard for IPv4 and/or IPv6'
   which means ':1234' can be used to set only the portnum.
2. Only add this protocol wildcard if --autolisten=1 (default)
   and no other addresses specified.
3. Pass it down to gossipd, so it can handle errors correctly: in most cases,
   it's fatal not to be able to bind to a port, but for this case, it's OK
   if we can only bind to one of IPv4/v6 (fatal iff neither).

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 73cd009a4c
commit 52917ff6c9
10 changed files with 108 additions and 41 deletions

View File

@ -63,6 +63,9 @@ void towire_wireaddr_internal(u8 **pptr, const struct wireaddr_internal *addr)
towire_u8_array(pptr, (const u8 *)addr->u.sockname,
sizeof(addr->u.sockname));
return;
case ADDR_INTERNAL_ALLPROTO:
towire_u16(pptr, addr->u.port);
return;
case ADDR_INTERNAL_WIREADDR:
towire_wireaddr(pptr, &addr->u.wireaddr);
return;
@ -82,6 +85,9 @@ bool fromwire_wireaddr_internal(const u8 **cursor, size_t *max,
if (!memchr(addr->u.sockname, 0, sizeof(addr->u.sockname)))
fromwire_fail(cursor, max);
return *cursor != NULL;
case ADDR_INTERNAL_ALLPROTO:
addr->u.port = fromwire_u16(cursor, max);
return *cursor != NULL;
case ADDR_INTERNAL_WIREADDR:
return fromwire_wireaddr(cursor, max, &addr->u.wireaddr);
}
@ -164,6 +170,8 @@ char *fmt_wireaddr_internal(const tal_t *ctx,
switch (a->itype) {
case ADDR_INTERNAL_SOCKNAME:
return tal_fmt(ctx, "%s", a->u.sockname);
case ADDR_INTERNAL_ALLPROTO:
return tal_fmt(ctx, ":%u", a->u.port);
case ADDR_INTERNAL_WIREADDR:
return fmt_wireaddr(ctx, &a->u.wireaddr);
}
@ -297,8 +305,12 @@ finish:
return res;
}
bool parse_wireaddr_internal(const char *arg, struct wireaddr_internal *addr, u16 port, const char **err_msg)
bool parse_wireaddr_internal(const char *arg, struct wireaddr_internal *addr,
u16 port, bool wildcard_ok, const char **err_msg)
{
u16 wildport;
char *ip;
/* Addresses starting with '/' are local socket paths */
if (arg[0] == '/') {
addr->itype = ADDR_INTERNAL_SOCKNAME;
@ -313,6 +325,16 @@ bool parse_wireaddr_internal(const char *arg, struct wireaddr_internal *addr, u1
return true;
}
/* An empty string means IPv4 and IPv6 (which under Linux by default
* means just IPv6, and IPv4 gets autobound). */
if (wildcard_ok
&& separate_address_and_port(tmpctx, arg, &ip, &wildport)
&& streq(ip, "")) {
addr->itype = ADDR_INTERNAL_ALLPROTO;
addr->u.port = wildport;
return true;
}
addr->itype = ADDR_INTERNAL_WIREADDR;
return parse_wireaddr(arg, &addr->u.wireaddr, port, err_msg);
}

View File

@ -77,6 +77,7 @@ bool wireaddr_to_ipv6(const struct wireaddr *addr, struct sockaddr_in6 *s6);
enum wireaddr_internal_type {
ADDR_INTERNAL_SOCKNAME,
ADDR_INTERNAL_ALLPROTO,
ADDR_INTERNAL_WIREADDR,
};
@ -84,11 +85,15 @@ enum wireaddr_internal_type {
struct wireaddr_internal {
enum wireaddr_internal_type itype;
union {
/* ADDR_INTERNAL_SOCKNAME */
struct wireaddr wireaddr;
/* ADDR_INTERNAL_ALLPROTO */
u16 port;
/* ADDR_INTERNAL_WIREADDR */
char sockname[108];
} u;
};
bool parse_wireaddr_internal(const char *arg, struct wireaddr_internal *addr, u16 port, const char **err_msg);
bool parse_wireaddr_internal(const char *arg, struct wireaddr_internal *addr, u16 port, bool wildcard_ok, const char **err_msg);
void towire_wireaddr_internal(u8 **pptr,
const struct wireaddr_internal *addr);

View File

@ -570,7 +570,8 @@ static u8 *create_node_announcement(const tal_t *ctx, struct daemon *daemon,
if (!(daemon->listen_announce[i] & ADDR_ANNOUNCE))
continue;
/* You can only announce wiretypes! */
assert(daemon->wireaddrs[i].itype == ADDR_INTERNAL_WIREADDR);
if (daemon->wireaddrs[i].itype != ADDR_INTERNAL_WIREADDR)
continue;
towire_wireaddr(&addresses,
&daemon->wireaddrs[i].u.wireaddr);
}
@ -1389,10 +1390,14 @@ out:
return daemon_conn_read_next(conn, &daemon->master);
}
static int make_listen_fd(int domain, void *addr, socklen_t len, bool reportfail)
static int make_listen_fd(int domain, void *addr, socklen_t len, bool mayfail)
{
int fd = socket(domain, SOCK_STREAM, 0);
if (fd < 0) {
if (!mayfail)
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Failed to create %u socket: %s",
domain, strerror(errno));
status_trace("Failed to create %u socket: %s",
domain, strerror(errno));
return -1;
@ -1407,17 +1412,20 @@ static int make_listen_fd(int domain, void *addr, socklen_t len, bool reportfail
strerror(errno));
if (bind(fd, addr, len) != 0) {
if (reportfail)
status_broken("Failed to bind on %u socket: %s",
if (!mayfail)
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Failed to bind on %u socket: %s",
domain, strerror(errno));
status_trace("Failed to create %u socket: %s",
domain, strerror(errno));
goto fail;
}
}
if (listen(fd, 5) != 0) {
status_broken("Failed to listen on %u socket: %s",
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Failed to listen on %u socket: %s",
domain, strerror(errno));
goto fail;
}
return fd;
@ -1561,10 +1569,10 @@ static struct io_plan *connection_in(struct io_conn *conn, struct daemon *daemon
init_new_peer, daemon);
}
/* Returns true if it was an IPv6 wildcard (as inserted by guess_addresses) */
/* Return true if it created socket successfully. */
static bool handle_wireaddr_listen(struct daemon *daemon,
const struct wireaddr *wireaddr,
bool had_ipv6_wildcard)
bool mayfail)
{
int fd;
struct sockaddr_in addr;
@ -1574,27 +1582,24 @@ static bool handle_wireaddr_listen(struct daemon *daemon,
case ADDR_TYPE_IPV4:
wireaddr_to_ipv4(wireaddr, &addr);
/* We might fail if IPv6 bound to port first */
fd = make_listen_fd(AF_INET, &addr, sizeof(addr),
!had_ipv6_wildcard);
fd = make_listen_fd(AF_INET, &addr, sizeof(addr), mayfail);
if (fd >= 0) {
status_trace("Created IPv4 listener on port %u",
wireaddr->port);
io_new_listener(daemon, fd, connection_in, daemon);
return true;
}
return false;
case ADDR_TYPE_IPV6:
wireaddr_to_ipv6(wireaddr, &addr6);
if (memeqzero(&addr6.sin6_addr, sizeof(addr6.sin6_addr)))
had_ipv6_wildcard = true;
else
had_ipv6_wildcard = false;
fd = make_listen_fd(AF_INET6, &addr6, sizeof(addr6), true);
fd = make_listen_fd(AF_INET6, &addr6, sizeof(addr6), mayfail);
if (fd >= 0) {
status_trace("Created IPv6 listener on port %u",
wireaddr->port);
io_new_listener(daemon, fd, connection_in, daemon);
return true;
}
return had_ipv6_wildcard;
return false;
case ADDR_TYPE_PADDING:
break;
}
@ -1604,11 +1609,12 @@ static bool handle_wireaddr_listen(struct daemon *daemon,
static void setup_listeners(struct daemon *daemon)
{
bool had_ipv6_wildcard = false;
struct sockaddr_un addrun;
int fd;
for (size_t i = 0; i < tal_count(daemon->wireaddrs); i++) {
struct wireaddr wa = daemon->wireaddrs[i].u.wireaddr;
if (!(daemon->listen_announce[i] & ADDR_LISTEN))
continue;
@ -1623,10 +1629,22 @@ static void setup_listeners(struct daemon *daemon)
addrun.sun_path);
io_new_listener(daemon, fd, connection_in, daemon);
continue;
case ADDR_INTERNAL_ALLPROTO: {
bool ipv6_ok;
memset(wa.addr, 0, sizeof(wa.addr));
wa.type = ADDR_TYPE_IPV6;
wa.addrlen = 16;
ipv6_ok = handle_wireaddr_listen(daemon, &wa, true);
wa.type = ADDR_TYPE_IPV4;
wa.addrlen = 4;
/* OK if this fails, as long as one succeeds! */
handle_wireaddr_listen(daemon, &wa, ipv6_ok);
continue;
}
case ADDR_INTERNAL_WIREADDR:
had_ipv6_wildcard = handle_wireaddr_listen(
daemon, &daemon->wireaddrs[i].u.wireaddr,
had_ipv6_wildcard);
handle_wireaddr_listen(daemon, &wa, false);
continue;
}
/* Shouldn't happen. */
@ -1673,19 +1691,12 @@ static struct io_plan *gossip_activate(struct daemon_conn *master,
const u8 *msg)
{
bool listen;
bool guess_addrs;
u16 port;
if (!fromwire_gossipctl_activate(msg, &listen, &guess_addrs, &port))
if (!fromwire_gossipctl_activate(msg, &listen))
master_badmsg(WIRE_GOSSIPCTL_ACTIVATE, msg);
if (listen) {
if (guess_addrs)
guess_addresses(&daemon->wireaddrs,
&daemon->listen_announce,
port);
if (listen)
setup_listeners(daemon);
}
/* OK, we're ready! */
daemon_conn_send(&daemon->master,
@ -1808,6 +1819,10 @@ static struct io_plan *conn_init(struct io_conn *conn, struct reaching *reach)
ai.ai_addrlen = sizeof(sin);
ai.ai_addr = (struct sockaddr *)&sun;
break;
case ADDR_INTERNAL_ALLPROTO:
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Can't reach to all protocols");
break;
case ADDR_INTERNAL_WIREADDR:
switch (reach->addr.u.wireaddr.type) {
case ADDR_TYPE_IPV4:
@ -1950,6 +1965,9 @@ static void try_reach_peer(struct daemon *daemon, const struct pubkey *id,
case ADDR_INTERNAL_SOCKNAME:
af = AF_LOCAL;
break;
case ADDR_INTERNAL_ALLPROTO:
status_failed(STATUS_FAIL_INTERNAL_ERROR,
"Can't reach ALLPROTO");
case ADDR_INTERNAL_WIREADDR:
switch (a->addr.u.wireaddr.type) {
case ADDR_TYPE_IPV4:

View File

@ -23,9 +23,6 @@ gossipctl_init,,reconnect,bool
gossipctl_activate,3025
# Do we listen?
gossipctl_activate,,listen,bool
gossipctl_activate,,guess_addresses,bool
# FIXME: Hack for deprecated --port option.
gossipctl_activate,,port,u16
# Gossipd->master, I am ready, here are the final addresses.
gossipctl_activate_reply,3125

1 #include <common/cryptomsg.h>
23 gossipctl_activate,,listen,bool
24 gossipctl_activate,,guess_addresses,bool # Gossipd->master, I am ready, here are the final addresses.
25 # FIXME: Hack for deprecated --port option. gossipctl_activate_reply,3125
gossipctl_activate,,port,u16
# Gossipd->master, I am ready, here are the final addresses.
gossipctl_activate_reply,3125
26 gossipctl_activate_reply,,num_wireaddrs,u16
27 gossipctl_activate_reply,,wireaddrs,num_wireaddrs*struct wireaddr_internal
28 gossipctl_activate_reply,,listen_announce,num_wireaddrs*enum addr_listen_announce

View File

@ -151,7 +151,8 @@ static void json_connect(struct command *cmd,
} else {
port = DEFAULT_PORT;
}
if (!parse_wireaddr_internal(name, &addr, port, &err_msg)) {
if (!parse_wireaddr_internal(name, &addr, port, false,
&err_msg)) {
command_fail(cmd, "Host %s:%u not valid: %s",
name, port, err_msg ? err_msg : "port is 0");
return;

View File

@ -183,6 +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;
msg = towire_hsm_client_hsmfd(tmpctx, &ld->id, capabilities);
if (!wire_sync_write(ld->hsm_fd, msg))
@ -202,12 +204,21 @@ void gossip_init(struct lightningd *ld)
if (!ld->gossip)
err(1, "Could not subdaemon gossip");
/* If no addr specified, hand wildcard to gossipd */
if (tal_count(wireaddrs) == 0 && ld->autolisten) {
wireaddrs = tal_arrz(tmpctx, struct wireaddr_internal, 1);
listen_announce = tal_arr(tmpctx, enum addr_listen_announce, 1);
wireaddrs->itype = ADDR_INTERNAL_ALLPROTO;
wireaddrs->u.port = ld->portnum;
*listen_announce = ADDR_LISTEN_AND_ANNOUNCE;
}
msg = towire_gossipctl_init(
tmpctx, ld->config.broadcast_interval,
&get_chainparams(ld)->genesis_blockhash, &ld->id,
get_offered_global_features(tmpctx),
get_offered_local_features(tmpctx), ld->wireaddrs,
ld->listen_announce, ld->rgb,
get_offered_local_features(tmpctx), wireaddrs,
listen_announce, ld->rgb,
ld->alias, ld->config.channel_update_interval, ld->reconnect);
subd_send_msg(ld->gossip, msg);
}
@ -234,8 +245,7 @@ static void gossip_activate_done(struct subd *gossip UNUSED,
void gossip_activate(struct lightningd *ld)
{
const u8 *msg = towire_gossipctl_activate(NULL, ld->listen, ld->autolisten,
ld->portnum);
const u8 *msg = towire_gossipctl_activate(NULL, ld->listen);
subd_req(ld->gossip, ld->gossip, take(msg), -1, 0,
gossip_activate_done, NULL);

View File

@ -154,6 +154,12 @@ void json_add_address_internal(struct json_result *response,
json_add_string(response, "socket", addr->u.sockname);
json_object_end(response);
return;
case ADDR_INTERNAL_ALLPROTO:
json_object_start(response, fieldname);
json_add_string(response, "type", "any protocol");
json_add_num(response, "port", addr->u.port);
json_object_end(response);
return;
case ADDR_INTERNAL_WIREADDR:
json_add_address(response, fieldname, &addr->u.wireaddr);
return;

View File

@ -158,7 +158,8 @@ static char *opt_add_addr_withtype(const char *arg,
tal_resize(&ld->listen_announce, n+1);
ld->listen_announce[n] = ala;
if (!parse_wireaddr_internal(arg, &ld->wireaddrs[n], ld->portnum, &err_msg)) {
if (!parse_wireaddr_internal(arg, &ld->wireaddrs[n], ld->portnum,
true, &err_msg)) {
return tal_fmt(NULL, "Unable to parse address '%s': %s", arg, err_msg);
}

View File

@ -4281,6 +4281,13 @@ class LightningDTests(BaseLightningDTests):
bitcoind.generate_block(1)
l1.daemon.wait_for_log('ONCHAIN')
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
def test_listconfigs(self):
l1 = self.node_factory.get_node()

View File

@ -510,7 +510,7 @@ static struct peer *wallet_peer_load(struct wallet *w, const u64 dbid)
addrstr = sqlite3_column_text(stmt, 2);
if (addrstr) {
addrp = &addr;
if (!parse_wireaddr_internal((const char*)addrstr, addrp, DEFAULT_PORT, NULL)) {
if (!parse_wireaddr_internal((const char*)addrstr, addrp, DEFAULT_PORT, false, NULL)) {
db_stmt_done(stmt);
return NULL;
}