mirror of
https://github.com/aljazceru/lightning.git
synced 2025-12-23 17:14:22 +01:00
connectd: fix binding to same port on IPv4 and IPv6.
1. If the IPv6 address was public, that changed the wireaddr and thus the ipv4 bind would not be to a wildcard and would fail. 2. Binding two fds to the same port on both wildcard IPv4 and IPv6 succeeds; we only fail when we try to listen, so allow error at this point. For some reason this triggered on my digital ocean machine. Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
This commit is contained in:
committed by
Christian Decker
parent
17f7f50814
commit
d8a6028214
@@ -103,6 +103,15 @@ HTABLE_DEFINE_TYPE(struct important_peerid,
|
|||||||
important_peerid_eq,
|
important_peerid_eq,
|
||||||
important_peerid_map);
|
important_peerid_map);
|
||||||
|
|
||||||
|
struct listen_fd {
|
||||||
|
int fd;
|
||||||
|
/* If we bind() IPv6 then IPv4 to same port, we *may* fail to listen()
|
||||||
|
* on the IPv4 socket: under Linux, by default, the IPv6 listen()
|
||||||
|
* covers IPv4 too. Normally we'd consider failing to listen on a
|
||||||
|
* port to be fatal, so we note this when setting up addresses. */
|
||||||
|
bool mayfail;
|
||||||
|
};
|
||||||
|
|
||||||
struct daemon {
|
struct daemon {
|
||||||
/* Who am I? */
|
/* Who am I? */
|
||||||
struct pubkey id;
|
struct pubkey id;
|
||||||
@@ -155,7 +164,7 @@ struct daemon {
|
|||||||
struct sockaddr *broken_resolver_response;
|
struct sockaddr *broken_resolver_response;
|
||||||
|
|
||||||
/* File descriptors to listen on once we're activated. */
|
/* File descriptors to listen on once we're activated. */
|
||||||
int *listen_fds;
|
struct listen_fd *listen_fds;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Peers we're trying to reach. */
|
/* Peers we're trying to reach. */
|
||||||
@@ -1050,11 +1059,12 @@ static struct io_plan *connection_in(struct io_conn *conn, struct daemon *daemon
|
|||||||
init_new_peer, daemon);
|
init_new_peer, daemon);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void add_listen_fd(struct daemon *daemon, int fd)
|
static void add_listen_fd(struct daemon *daemon, int fd, bool mayfail)
|
||||||
{
|
{
|
||||||
size_t n = tal_count(daemon->listen_fds);
|
size_t n = tal_count(daemon->listen_fds);
|
||||||
tal_resize(&daemon->listen_fds, n+1);
|
tal_resize(&daemon->listen_fds, n+1);
|
||||||
daemon->listen_fds[n] = fd;
|
daemon->listen_fds[n].fd = fd;
|
||||||
|
daemon->listen_fds[n].mayfail = mayfail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Return true if it created socket successfully. */
|
/* Return true if it created socket successfully. */
|
||||||
@@ -1074,7 +1084,7 @@ static bool handle_wireaddr_listen(struct daemon *daemon,
|
|||||||
if (fd >= 0) {
|
if (fd >= 0) {
|
||||||
status_trace("Created IPv4 listener on port %u",
|
status_trace("Created IPv4 listener on port %u",
|
||||||
wireaddr->port);
|
wireaddr->port);
|
||||||
add_listen_fd(daemon, fd);
|
add_listen_fd(daemon, fd, mayfail);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -1084,7 +1094,7 @@ static bool handle_wireaddr_listen(struct daemon *daemon,
|
|||||||
if (fd >= 0) {
|
if (fd >= 0) {
|
||||||
status_trace("Created IPv6 listener on port %u",
|
status_trace("Created IPv6 listener on port %u",
|
||||||
wireaddr->port);
|
wireaddr->port);
|
||||||
add_listen_fd(daemon, fd);
|
add_listen_fd(daemon, fd, mayfail);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -1206,7 +1216,7 @@ static struct wireaddr_internal *setup_listeners(const tal_t *ctx,
|
|||||||
false);
|
false);
|
||||||
status_trace("Created socket listener on file %s",
|
status_trace("Created socket listener on file %s",
|
||||||
addrun.sun_path);
|
addrun.sun_path);
|
||||||
add_listen_fd(daemon, fd);
|
add_listen_fd(daemon, fd, false);
|
||||||
/* We don't announce socket names */
|
/* We don't announce socket names */
|
||||||
assert(!announce);
|
assert(!announce);
|
||||||
add_binding(&binding, &wa);
|
add_binding(&binding, &wa);
|
||||||
@@ -1219,12 +1229,12 @@ static struct wireaddr_internal *setup_listeners(const tal_t *ctx,
|
|||||||
|
|
||||||
wa.itype = ADDR_INTERNAL_WIREADDR;
|
wa.itype = ADDR_INTERNAL_WIREADDR;
|
||||||
wa.u.wireaddr.port = wa.u.port;
|
wa.u.wireaddr.port = wa.u.port;
|
||||||
memset(wa.u.wireaddr.addr, 0,
|
|
||||||
sizeof(wa.u.wireaddr.addr));
|
|
||||||
|
|
||||||
/* Try both IPv6 and IPv4. */
|
/* First, create wildcard IPv6 address. */
|
||||||
wa.u.wireaddr.type = ADDR_TYPE_IPV6;
|
wa.u.wireaddr.type = ADDR_TYPE_IPV6;
|
||||||
wa.u.wireaddr.addrlen = 16;
|
wa.u.wireaddr.addrlen = 16;
|
||||||
|
memset(wa.u.wireaddr.addr, 0,
|
||||||
|
sizeof(wa.u.wireaddr.addr));
|
||||||
|
|
||||||
ipv6_ok = handle_wireaddr_listen(daemon, &wa.u.wireaddr,
|
ipv6_ok = handle_wireaddr_listen(daemon, &wa.u.wireaddr,
|
||||||
true);
|
true);
|
||||||
@@ -1234,8 +1244,12 @@ static struct wireaddr_internal *setup_listeners(const tal_t *ctx,
|
|||||||
&& public_address(daemon, &wa.u.wireaddr))
|
&& public_address(daemon, &wa.u.wireaddr))
|
||||||
add_announcable(daemon, &wa.u.wireaddr);
|
add_announcable(daemon, &wa.u.wireaddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Now, create wildcard IPv4 address. */
|
||||||
wa.u.wireaddr.type = ADDR_TYPE_IPV4;
|
wa.u.wireaddr.type = ADDR_TYPE_IPV4;
|
||||||
wa.u.wireaddr.addrlen = 4;
|
wa.u.wireaddr.addrlen = 4;
|
||||||
|
memset(wa.u.wireaddr.addr, 0,
|
||||||
|
sizeof(wa.u.wireaddr.addr));
|
||||||
/* OK if this fails, as long as one succeeds! */
|
/* OK if this fails, as long as one succeeds! */
|
||||||
if (handle_wireaddr_listen(daemon, &wa.u.wireaddr,
|
if (handle_wireaddr_listen(daemon, &wa.u.wireaddr,
|
||||||
ipv6_ok)) {
|
ipv6_ok)) {
|
||||||
@@ -1340,11 +1354,16 @@ static struct io_plan *connect_activate(struct daemon_conn *master,
|
|||||||
|
|
||||||
if (do_listen) {
|
if (do_listen) {
|
||||||
for (size_t i = 0; i < tal_count(daemon->listen_fds); i++) {
|
for (size_t i = 0; i < tal_count(daemon->listen_fds); i++) {
|
||||||
if (listen(daemon->listen_fds[i], 5) != 0)
|
/* On Linux, at least, we may bind to all addresses
|
||||||
|
* for IPv4 and IPv6, but we'll fail to listen. */
|
||||||
|
if (listen(daemon->listen_fds[i].fd, 5) != 0) {
|
||||||
|
if (daemon->listen_fds[i].mayfail)
|
||||||
|
continue;
|
||||||
status_failed(STATUS_FAIL_INTERNAL_ERROR,
|
status_failed(STATUS_FAIL_INTERNAL_ERROR,
|
||||||
"Failed to listen on socket: %s",
|
"Failed to listen on socket: %s",
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
io_new_listener(daemon, daemon->listen_fds[i],
|
}
|
||||||
|
io_new_listener(daemon, daemon->listen_fds[i].fd,
|
||||||
connection_in, daemon);
|
connection_in, daemon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1930,7 +1949,7 @@ int main(int argc, char *argv[])
|
|||||||
important_peerid_map_init(&daemon->important_peerids);
|
important_peerid_map_init(&daemon->important_peerids);
|
||||||
timers_init(&daemon->timers, time_mono());
|
timers_init(&daemon->timers, time_mono());
|
||||||
daemon->broken_resolver_response = NULL;
|
daemon->broken_resolver_response = NULL;
|
||||||
daemon->listen_fds = tal_arr(daemon, int, 0);
|
daemon->listen_fds = tal_arr(daemon, struct listen_fd, 0);
|
||||||
/* stdin == control */
|
/* stdin == control */
|
||||||
daemon_conn_init(daemon, &daemon->master, STDIN_FILENO, recv_req,
|
daemon_conn_init(daemon, &daemon->master, STDIN_FILENO, recv_req,
|
||||||
master_gone);
|
master_gone);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from fixtures import * # noqa: F401,F403
|
|||||||
from flaky import flaky
|
from flaky import flaky
|
||||||
from lightning import RpcError
|
from lightning import RpcError
|
||||||
from utils import DEVELOPER, sync_blockheight, only_one, wait_for
|
from utils import DEVELOPER, sync_blockheight, only_one, wait_for
|
||||||
|
from ephemeral_port_reserve import reserve
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@@ -772,3 +772,24 @@ def test_reserve_enforcement(node_factory, executor):
|
|||||||
'Peer permanent failure in CHANNELD_NORMAL: lightning_channeld: sent '
|
'Peer permanent failure in CHANNELD_NORMAL: lightning_channeld: sent '
|
||||||
'ERROR Bad peer_add_htlc: CHANNEL_ERR_CHANNEL_CAPACITY_EXCEEDED'
|
'ERROR Bad peer_add_htlc: CHANNEL_ERR_CHANNEL_CAPACITY_EXCEEDED'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_ipv4_and_ipv6(node_factory):
|
||||||
|
"""Test we can bind to both IPv4 and IPv6 addresses (if supported)"""
|
||||||
|
port = reserve()
|
||||||
|
l1 = node_factory.get_node(options={'addr': ':{}'.format(port)})
|
||||||
|
bind = l1.rpc.getinfo()['binding']
|
||||||
|
|
||||||
|
if len(bind) == 2:
|
||||||
|
assert bind[0]['type'] == 'ipv6'
|
||||||
|
assert bind[0]['address'] == '::'
|
||||||
|
assert int(bind[0]['port']) == port
|
||||||
|
assert bind[1]['type'] == 'ipv4'
|
||||||
|
assert bind[1]['address'] == '0.0.0.0'
|
||||||
|
assert int(bind[1]['port']) == port
|
||||||
|
else:
|
||||||
|
# Assume we're IPv4 only...
|
||||||
|
assert len(bind) == 1
|
||||||
|
assert bind[0]['type'] == 'ipv4'
|
||||||
|
assert bind[0]['address'] == '0.0.0.0'
|
||||||
|
assert int(bind[0]['port']) == port
|
||||||
|
|||||||
Reference in New Issue
Block a user