Skip to content

Commit b05d3e7

Browse files
committed
Merge bitcoin/bitcoin#22079: zmq: Add support to listen on IPv6 addresses
e699883 doc: Add IPv6 address to zmq example (nthumann) 8abe570 test: Add IPv6 test to zmq (nthumann) ded449b zmq: Enable IPv6 on listening socket (nthumann) Pull request description: This PR adds support for listening on IPv6 addresses with bitcoinds ZMQ interface, just like the RPC server. Currently, it is not possible to specify an IPv6 address, as the `ZMQ_IPV6` [socket option](http://api.zeromq.org/master:zmq-setsockopt#toc27) is not set and therefore the ZMQ initialization fails, if one does so. The absence of this option has also been noted [here](bitcoin/bitcoin#15198 (comment)). With this PR one can e.g. set `-zmqpubhashblock=tcp://[::1]:28333` to listen on the IPv6 loopback address. ACKs for top commit: laanwj: Code review ACK e699883 theStack: Tested ACK e699883 🌱 Tree-SHA512: 43c3043d8d5c79794d475926259c1be975b694db4fcc1f7750a9a28e242f0fa1b531735a63ea5777498003aa5834f6243f39742d0f3941f2f37593d0c7890700
2 parents a5d00d4 + e699883 commit b05d3e7

File tree

3 files changed

+50
-1
lines changed

3 files changed

+50
-1
lines changed

doc/zmq.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ For instance:
8484

8585
$ bitcoind -zmqpubhashtx=tcp://127.0.0.1:28332 \
8686
-zmqpubhashtx=tcp://192.168.1.2:28332 \
87+
-zmqpubhashblock="tcp://[::1]:28333" \
8788
-zmqpubrawtx=ipc:///tmp/bitcoind.tx.raw \
8889
-zmqpubhashtxhwm=10000
8990

@@ -125,6 +126,9 @@ Setting the keepalive values appropriately for your operating environment may
125126
improve connectivity in situations where long-lived connections are silently
126127
dropped by network middle boxes.
127128

129+
Also, the socket's ZMQ_IPV6 option is enabled to accept connections from IPv6
130+
hosts as well. If needed, this option has to be set on the client side too.
131+
128132
## Remarks
129133

130134
From the perspective of bitcoind, the ZeroMQ socket is write-only; PUB

src/zmq/zmqpublishnotifier.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include <chain.h>
88
#include <chainparams.h>
9+
#include <netbase.h>
910
#include <node/blockstorage.h>
1011
#include <rpc/server.h>
1112
#include <streams.h>
@@ -73,6 +74,20 @@ static int zmq_send_multipart(void *sock, const void* data, size_t size, ...)
7374
return 0;
7475
}
7576

77+
static bool IsZMQAddressIPV6(const std::string &zmq_address)
78+
{
79+
const std::string tcp_prefix = "tcp://";
80+
const size_t tcp_index = zmq_address.rfind(tcp_prefix);
81+
const size_t colon_index = zmq_address.rfind(":");
82+
if (tcp_index == 0 && colon_index != std::string::npos) {
83+
const std::string ip = zmq_address.substr(tcp_prefix.length(), colon_index - tcp_prefix.length());
84+
CNetAddr addr;
85+
LookupHost(ip, addr, false);
86+
if (addr.IsIPv6()) return true;
87+
}
88+
return false;
89+
}
90+
7691
bool CZMQAbstractPublishNotifier::Initialize(void *pcontext)
7792
{
7893
assert(!psocket);
@@ -107,6 +122,15 @@ bool CZMQAbstractPublishNotifier::Initialize(void *pcontext)
107122
return false;
108123
}
109124

125+
// On some systems (e.g. OpenBSD) the ZMQ_IPV6 must not be enabled, if the address to bind isn't IPv6
126+
const int enable_ipv6 { IsZMQAddressIPV6(address) ? 1 : 0};
127+
rc = zmq_setsockopt(psocket, ZMQ_IPV6, &enable_ipv6, sizeof(enable_ipv6));
128+
if (rc != 0) {
129+
zmqError("Failed to set ZMQ_IPV6");
130+
zmq_close(psocket);
131+
return false;
132+
}
133+
110134
rc = zmq_bind(psocket, address.c_str());
111135
if (rc != 0)
112136
{

test/functional/interface_zmq.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
assert_equal,
2525
assert_raises_rpc_error,
2626
)
27+
from test_framework.netutil import test_ipv6_local
2728
from io import BytesIO
2829
from time import sleep
2930

@@ -119,17 +120,20 @@ def run_test(self):
119120
self.test_mempool_sync()
120121
self.test_reorg()
121122
self.test_multiple_interfaces()
123+
self.test_ipv6()
122124
finally:
123125
# Destroy the ZMQ context.
124126
self.log.debug("Destroying ZMQ context")
125127
self.ctx.destroy(linger=None)
126128

127129
# Restart node with the specified zmq notifications enabled, subscribe to
128130
# all of them and return the corresponding ZMQSubscriber objects.
129-
def setup_zmq_test(self, services, *, recv_timeout=60, sync_blocks=True):
131+
def setup_zmq_test(self, services, *, recv_timeout=60, sync_blocks=True, ipv6=False):
130132
subscribers = []
131133
for topic, address in services:
132134
socket = self.ctx.socket(zmq.SUB)
135+
if ipv6:
136+
socket.setsockopt(zmq.IPV6, 1)
133137
subscribers.append(ZMQSubscriber(socket, topic.encode()))
134138

135139
self.restart_node(0, [f"-zmqpub{topic}={address}" for topic, address in services] +
@@ -568,5 +572,22 @@ def test_multiple_interfaces(self):
568572
assert_equal(self.nodes[0].getbestblockhash(), subscribers[0].receive().hex())
569573
assert_equal(self.nodes[0].getbestblockhash(), subscribers[1].receive().hex())
570574

575+
def test_ipv6(self):
576+
if not test_ipv6_local():
577+
self.log.info("Skipping IPv6 test, because IPv6 is not supported.")
578+
return
579+
self.log.info("Testing IPv6")
580+
# Set up subscriber using IPv6 loopback address
581+
subscribers = self.setup_zmq_test([
582+
("hashblock", "tcp://[::1]:28332")
583+
], ipv6=True)
584+
585+
# Generate 1 block in nodes[0]
586+
self.nodes[0].generatetoaddress(1, ADDRESS_BCRT1_UNSPENDABLE)
587+
588+
# Should receive the same block hash
589+
assert_equal(self.nodes[0].getbestblockhash(), subscribers[0].receive().hex())
590+
591+
571592
if __name__ == '__main__':
572593
ZMQTest().main()

0 commit comments

Comments
 (0)