11#include " ../src/cancellable_streambuf.h"
22#include " catch.hpp"
33#include < boost/asio/io_context.hpp>
4+ #include < boost/asio/ip/multicast.hpp>
45#include < boost/asio/ip/tcp.hpp>
56#include < boost/asio/ip/udp.hpp>
67#include < boost/asio/ip/v6_only.hpp>
1213#include < thread>
1314
1415namespace asio = lslboost::asio;
15- namespace ip = lslboost:: asio::ip ;
16- using lslboost::system::error_code;
16+ using namespace asio ;
17+ using err_t = const lslboost::system::error_code & ;
1718typedef lsl::cancellable_streambuf cancellable_streambuf;
1819
19- const uint16_t port = 28812 ;
20+ static uint16_t port = 28812 ;
21+ static const char hello[] = " Hello World" ;
22+ static const std::string hellostr (hello);
2023
2124static std::mutex output_mutex;
2225
26+ asio::const_buffer hellobuf () { return asio::const_buffer (hello, sizeof (hello)); }
27+
2328#define MINFO (str ) \
2429 { \
2530 std::unique_lock<std::mutex> out_lock (output_mutex); \
@@ -50,7 +55,7 @@ template <typename T> void test_cancel_thread(T &&task, cancellable_streambuf &s
5055
5156 if (future.wait_for (std::chrono::milliseconds (200 )) == std::future_status::ready)
5257 MINFO (" Thread 1 finished too soon, couldn't test cancellation" )
53- MINFO (" Thread 0: Closing socket…" );
58+ MINFO (" Thread 0: Closing socket…" )
5459 sb.cancel ();
5560 // Double cancel, shouldn't do anything dramatic
5661 sb.cancel ();
@@ -68,7 +73,7 @@ TEST_CASE("streambufs can connect", "[streambuf][basic][network]") {
6873 asio::io_context io_ctx;
6974 cancellable_streambuf sb_connect;
7075 INFO (" Thread 0: Binding remote socket and keeping it busy…" )
71- ip::tcp::endpoint ep (ip::address_v4::loopback (), port + 1 );
76+ ip::tcp::endpoint ep (ip::address_v4::loopback (), port++ );
7277 ip::tcp::acceptor remote (io_ctx);
7378 remote.open (ip::tcp::v4 ());
7479 remote.bind (ep);
@@ -98,7 +103,7 @@ TEST_CASE("streambufs can connect", "[streambuf][basic][network]") {
98103TEST_CASE (" streambufs can transfer data" , " [streambuf][network]" ) {
99104 asio::io_context io_ctx;
100105 cancellable_streambuf sb_read;
101- ip::tcp::endpoint ep (ip::address_v4::loopback (), port + 1 );
106+ ip::tcp::endpoint ep (ip::address_v4::loopback (), port++ );
102107 ip::tcp::acceptor remote (io_ctx, ep, true );
103108 remote.listen (1 );
104109 INFO (" Thread 0: Connecting…" )
@@ -116,24 +121,91 @@ TEST_CASE("streambufs can transfer data", "[streambuf][network]") {
116121}
117122
118123TEST_CASE (" receive v4 packets on v6 socket" , " [ipv6][network]" ) {
119- const uint16_t test_port = port + 2 ;
124+ const uint16_t test_port = port++ ;
120125 asio::io_context io_ctx;
121126 ip::udp::socket sock (io_ctx, ip::udp::v6 ());
122127 sock.set_option (ip::v6_only (false ));
123128 sock.bind (ip::udp::endpoint (ip::address_v6::any (), test_port));
124129
125130 ip::udp::socket sender_v4 (io_ctx, ip::udp::v4 ()), sender_v6 (io_ctx, ip::udp::v6 ());
126- const std::string buf = " Hello World" ;
127- asio::const_buffer sbuf (buf.data (), buf.length ());
131+ asio::const_buffer sbuf (hellobuf ());
128132 char recvbuf[64 ] = {0 };
129133 sender_v4.send_to (sbuf, ip::udp::endpoint (ip::address_v4::loopback (), test_port));
130134 auto recv_len = sock.receive (asio::buffer (recvbuf, sizeof (recvbuf) - 1 ));
131- CHECK (recv_len == buf. length ( ));
132- CHECK (buf == recvbuf);
135+ CHECK (recv_len == sizeof (hello ));
136+ CHECK (hellostr == recvbuf);
133137 std::fill_n (recvbuf, recv_len, 0 );
134138
135139 sender_v6.send_to (sbuf, ip::udp::endpoint (ip::address_v6::loopback (), test_port));
136140 recv_len = sock.receive (asio::buffer (recvbuf, sizeof (recvbuf) - 1 ));
137- CHECK (buf == recvbuf);
141+ CHECK (hellostr == recvbuf);
138142 std::fill_n (recvbuf, recv_len, 0 );
139143}
144+
145+ TEST_CASE (" ipaddresses" , " [ipv6][network][basic]" ) {
146+ ip::address_v4 v4addr (ip::make_address_v4 (" 192.168.172.1" )),
147+ mcastv4 (ip::make_address_v4 (" 239.0.0.183" ));
148+ ip::address_v6 v6addr = ip::make_address_v6 (ip::v4_mapped_t (), v4addr);
149+ ip::address addr (v4addr), addr_mapped (v6addr);
150+ CHECK (!v4addr.is_multicast ());
151+ CHECK (mcastv4.is_multicast ());
152+ // mapped IPv4 multicast addresses aren't considered IPv6 multicast addresses
153+ CHECK (!ip::make_address_v6 (ip::v4_mapped, mcastv4).is_multicast ());
154+ CHECK (v6addr.is_v4_mapped ());
155+ CHECK (addr != addr_mapped);
156+ CHECK (addr == ip::address (ip::make_address_v4 (ip::v4_mapped, v6addr)));
157+
158+ auto scoped = ip::make_address_v6 (" ::1%3" );
159+ CHECK (scoped.scope_id () == 3 );
160+ }
161+
162+ // / Can multiple sockets bind to the same port and receive all broad-/multicast packets?
163+ TEST_CASE (" reuseport" , " [network][basic][!mayfail]" ) {
164+ const uint16_t test_port = port++;
165+ asio::io_context io_ctx (1 );
166+ lslboost::system::error_code ec;
167+ // Linux: sudo ip link set lo multicast on; sudo ip mroute show table all
168+ for (auto addrstr : {" 224.0.0.1" , " 255.255.255.255" , " ff02::1" }) SECTION (addrstr) {
169+ std::vector<ip::udp::socket> socks;
170+ auto addr = ip::make_address (addrstr);
171+ if (!addr.is_multicast ())
172+ REQUIRE ((addr.is_v4 () && addr.to_v4 () == ip::address_v4::broadcast ()));
173+ auto proto = addr.is_v4 () ? ip::udp::v4 () : ip::udp::v6 ();
174+ for (int i = 0 ; i < 2 ; ++i) {
175+ socks.emplace_back (io_ctx, proto);
176+ auto &sock = socks.back ();
177+ sock.set_option (ip::udp::socket::reuse_address (true ));
178+ if (addr.is_multicast ()) sock.set_option (ip::multicast::join_group (addr), ec);
179+ if (ec == error::no_such_device || ec == std::errc::address_not_available)
180+ FAIL (" No IPv6 route configured, skipping test!" );
181+ sock.bind (ip::udp::endpoint (proto, test_port));
182+ }
183+ {
184+ ip::udp::socket outsock (io_ctx, proto);
185+ if (addr.is_multicast ())
186+ outsock.set_option (ip::multicast::join_group (addr));
187+ else
188+ outsock.set_option (ip::udp::socket::broadcast (true ));
189+ // outsock.set_option(ip::multicast::enable_loopback(true));
190+ auto sent = outsock.send_to (hellobuf (), ip::udp::endpoint (addr, test_port));
191+ REQUIRE (sent == sizeof (hello));
192+ outsock.close ();
193+ }
194+ char inbuf[sizeof (hello)] = {0 };
195+ std::size_t received = 0 ;
196+
197+ asio::steady_timer timeout (io_ctx, std::chrono::seconds (2 ));
198+ timeout.async_wait ([](err_t err) {
199+ if (!err) throw std::runtime_error (" Test didn't finish in time" );
200+ });
201+ for (auto &insock : socks)
202+ insock.async_receive (
203+ asio::buffer (inbuf, sizeof (inbuf)), [&](err_t , std::size_t len) {
204+ CHECK (len == sizeof (hello));
205+ CHECK (hellostr == inbuf);
206+ received++;
207+ });
208+ while (received < socks.size ()) io_ctx.run_one ();
209+ timeout.cancel ();
210+ }
211+ }
0 commit comments