-
Notifications
You must be signed in to change notification settings - Fork 2
Fix multicast build and validation logic #82
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
e62633b
1398c16
638e67d
b4007d4
d0c81a7
e3d0c1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
|
|
||
| #include "core.hpp" | ||
| #include <webcraft/async/fire_and_forget_task.hpp> | ||
| #include <stdexcept> | ||
|
|
||
| namespace webcraft::async::io::socket | ||
| { | ||
|
|
@@ -16,6 +17,81 @@ namespace webcraft::async::io::socket | |
| uint16_t port; | ||
| }; | ||
|
|
||
| /// Placeholder for options when joining a multicast group. Currently empty: an instance | ||
| /// of this type indicates default multicast join behavior. Fields may be added in the future. | ||
| struct multicast_join_options | ||
| { | ||
| }; | ||
|
|
||
| namespace detail | ||
| { | ||
| /// Returns true if the given address string is a valid IPv4 or IPv6 multicast address. | ||
| inline bool is_multicast_address(const std::string &addr) | ||
| { | ||
| if (addr.empty()) return false; | ||
| if (addr.find('.') != std::string::npos) | ||
| { | ||
| // IPv4: 224.0.0.0 - 239.255.255.255 | ||
| unsigned a = 0, b = 0, c = 0, d = 0; | ||
| int n = 0; | ||
| if (std::sscanf(addr.c_str(), "%u.%u.%u.%u%n", &a, &b, &c, &d, &n) != 4) return false; | ||
| if (addr.size() != static_cast<std::size_t>(n)) return false; | ||
| if (a > 255u || b > 255u || c > 255u || d > 255u) return false; | ||
| if (a < 224u || a > 239u) return false; | ||
| return true; | ||
| } | ||
| if (addr.find(':') != std::string::npos) | ||
| { | ||
| // IPv6: ff00::/8 — first 16-bit segment must be 0xff00–0xffff | ||
| std::size_t i = 0; | ||
| while (i < addr.size() && addr[i] == ':') ++i; | ||
| while (i < addr.size()) | ||
| { | ||
| std::size_t start = i; | ||
| while (i < addr.size() && addr[i] != ':') | ||
| { | ||
| char ch = addr[i]; | ||
| if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) | ||
| ++i; | ||
| else | ||
| return false; | ||
| } | ||
| if (i > start) | ||
| { | ||
| if (i - start >= 2u && i - start <= 4u) | ||
| { | ||
| if ((addr[start] == 'f' || addr[start] == 'F') && (addr[start + 1] == 'f' || addr[start + 1] == 'F')) | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
| if (i < addr.size()) ++i; | ||
| } | ||
| return false; | ||
| } | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /// Represents a multicast group address. Use resolve() to create from a string (e.g. "239.255.0.1"). | ||
| struct multicast_group | ||
| { | ||
| std::string host; ///< Multicast group address (e.g. "239.255.0.1") | ||
| uint16_t port{0}; ///< Port used when sending to the group; must be set to the desired (non-zero) UDP port before calling send functions. | ||
|
|
||
| /// Resolve a multicast group from an address string (IPv4 or IPv6 multicast address). | ||
| /// \throws std::invalid_argument if addr is not a valid multicast address. | ||
| static multicast_group resolve(std::string_view addr) | ||
| { | ||
| std::string s(addr); | ||
| if (!detail::is_multicast_address(s)) | ||
| throw std::invalid_argument("Not a multicast address: " + s); | ||
| multicast_group g; | ||
| g.host = std::move(s); | ||
| return g; | ||
| } | ||
| }; | ||
|
|
||
| enum class ip_version | ||
| { | ||
| IPv4, | ||
|
|
@@ -30,7 +106,6 @@ namespace webcraft::async::io::socket | |
|
|
||
| namespace detail | ||
| { | ||
|
|
||
| class tcp_descriptor_base | ||
| { | ||
| public: | ||
|
|
@@ -77,6 +152,11 @@ namespace webcraft::async::io::socket | |
|
|
||
| virtual task<size_t> recvfrom(std::span<char> buffer, connection_info &info) = 0; | ||
| virtual task<size_t> sendto(std::span<const char> buffer, const connection_info &info) = 0; | ||
|
|
||
| /// Join a multicast group. Optional; no-op if not supported (e.g. mock). | ||
| virtual void join_group(const multicast_group &group, const multicast_join_options &opts) { (void)group; (void)opts; } | ||
| /// Leave a multicast group. | ||
| virtual void leave_group(const multicast_group &group) { (void)group; } | ||
| }; | ||
|
|
||
| std::shared_ptr<tcp_socket_descriptor> make_tcp_socket_descriptor(); | ||
|
|
@@ -302,7 +382,7 @@ namespace webcraft::async::io::socket | |
|
|
||
| task<tcp_socket> accept() | ||
| { | ||
| co_return co_await descriptor->accept(); | ||
| co_return tcp_socket(co_await descriptor->accept()); | ||
|
||
| } | ||
|
|
||
| task<void> close() | ||
|
|
@@ -341,6 +421,16 @@ namespace webcraft::async::io::socket | |
| descriptor->bind(info); | ||
| } | ||
|
|
||
| void join(const multicast_group &group) | ||
| { | ||
| descriptor->join_group(group, multicast_join_options{}); | ||
| } | ||
|
|
||
| void leave(const multicast_group &group) | ||
| { | ||
| descriptor->leave_group(group); | ||
| } | ||
|
|
||
| task<size_t> recvfrom(std::span<char> buffer, connection_info &info) | ||
| { | ||
| return descriptor->recvfrom(buffer, info); | ||
|
|
@@ -350,8 +440,17 @@ namespace webcraft::async::io::socket | |
| { | ||
| return descriptor->sendto(buffer, info); | ||
| } | ||
|
|
||
| task<size_t> sendto(std::span<const char> buffer, const multicast_group &group) | ||
| { | ||
| connection_info info{group.host, group.port}; | ||
| return descriptor->sendto(buffer, info); | ||
| } | ||
| }; | ||
|
|
||
| /// Alias for UDP socket used in multicast contexts (same type; join/leave/sendto(group) available). | ||
| using multicast_socket = udp_socket; | ||
|
|
||
| inline tcp_socket make_tcp_socket() | ||
| { | ||
| return tcp_socket(detail::make_tcp_socket_descriptor()); | ||
|
|
@@ -366,4 +465,9 @@ namespace webcraft::async::io::socket | |
| { | ||
| return udp_socket(detail::make_udp_socket_descriptor(version)); | ||
| } | ||
|
|
||
| inline multicast_socket make_multicast_socket(std::optional<ip_version> version = std::nullopt) | ||
| { | ||
| return make_udp_socket(version); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,6 +10,7 @@ | |
| #include <webcraft/async/runtime/macos.event.hpp> | ||
| #include <webcraft/async/runtime/linux.event.hpp> | ||
| #include <cstdio> | ||
| #include <stdexcept> | ||
| #include <webcraft/async/thread_pool.hpp> | ||
| #include <webcraft/async/async_event.hpp> | ||
| #include <webcraft/net/util.hpp> | ||
|
|
@@ -31,6 +32,13 @@ using namespace webcraft::async::io::socket::detail; | |
| #include <WinSock2.h> | ||
| #include <WS2tcpip.h> | ||
| #include <MSWSock.h> | ||
| // Windows uses IPV6_ADD_MEMBERSHIP/IPV6_DROP_MEMBERSHIP; POSIX uses IPV6_JOIN_GROUP/IPV6_LEAVE_GROUP. | ||
| #ifndef IPV6_JOIN_GROUP | ||
| #define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP | ||
| #endif | ||
| #ifndef IPV6_LEAVE_GROUP | ||
| #define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP | ||
| #endif | ||
|
|
||
| #elif defined(__APPLE__) | ||
|
|
||
|
|
@@ -795,6 +803,64 @@ class io_uring_udp_socket_descriptor : public webcraft::async::io::socket::detai | |
|
|
||
| co_return bytes_sent; | ||
| } | ||
|
|
||
| void join_group(const webcraft::async::io::socket::multicast_group &group, const webcraft::async::io::socket::multicast_join_options &) override | ||
| { | ||
| if (socket < 0) return; | ||
| in_addr maddr4; | ||
| if (inet_pton(AF_INET, group.host.c_str(), &maddr4) == 1) | ||
| { | ||
| if (!IN_MULTICAST(ntohl(maddr4.s_addr))) | ||
| throw std::invalid_argument("Not a multicast address: " + group.host); | ||
| struct ip_mreq mreq{}; | ||
| mreq.imr_multiaddr = maddr4; | ||
| mreq.imr_interface.s_addr = INADDR_ANY; | ||
| if (setsockopt(socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) | ||
| { | ||
| std::error_code ec(errno, std::system_category()); | ||
| throw std::system_error(ec, "Failed to join IPv4 multicast group"); | ||
| } | ||
| return; | ||
| } | ||
| struct in6_addr maddr6; | ||
| if (inet_pton(AF_INET6, group.host.c_str(), &maddr6) == 1) | ||
| { | ||
| if (!IN6_IS_ADDR_MULTICAST(&maddr6)) | ||
| throw std::invalid_argument("Not a multicast address: " + group.host); | ||
| struct ipv6_mreq mreq6{}; | ||
| mreq6.ipv6mr_multiaddr = maddr6; | ||
| mreq6.ipv6mr_interface = 0; | ||
| if (setsockopt(socket, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq6, sizeof(mreq6)) < 0) | ||
| { | ||
| std::error_code ec(errno, std::system_category()); | ||
| throw std::system_error(ec, "Failed to join IPv6 multicast group"); | ||
| } | ||
| return; | ||
| } | ||
| throw std::invalid_argument("Invalid multicast address: " + group.host); | ||
| } | ||
|
|
||
| void leave_group(const webcraft::async::io::socket::multicast_group &group) override | ||
| { | ||
| if (socket < 0) return; | ||
| in_addr maddr4; | ||
| if (inet_pton(AF_INET, group.host.c_str(), &maddr4) == 1) | ||
| { | ||
| struct ip_mreq mreq{}; | ||
| mreq.imr_multiaddr = maddr4; | ||
| mreq.imr_interface.s_addr = INADDR_ANY; | ||
| setsockopt(socket, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); | ||
| return; | ||
| } | ||
| struct in6_addr maddr6; | ||
| if (inet_pton(AF_INET6, group.host.c_str(), &maddr6) == 1) | ||
| { | ||
| struct ipv6_mreq mreq6{}; | ||
| mreq6.ipv6mr_multiaddr = maddr6; | ||
| mreq6.ipv6mr_interface = 0; | ||
| setsockopt(socket, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq6, sizeof(mreq6)); | ||
| } | ||
|
Comment on lines
+843
to
+862
|
||
| } | ||
|
Comment on lines
+807
to
+863
|
||
| }; | ||
|
|
||
| std::shared_ptr<webcraft::async::io::socket::detail::udp_socket_descriptor> webcraft::async::io::socket::detail::make_udp_socket_descriptor(std::optional<webcraft::async::io::socket::ip_version> version) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The IPv6 multicast address validation logic is incomplete and will incorrectly validate many IPv6 addresses. The current implementation only checks if the first hexadecimal segment starts with 'ff', but this has several issues:
For correct validation, consider using
inet_pton(AF_INET6, ...)followed byIN6_IS_ADDR_MULTICAST()macro (which checks if the first byte is 0xff), similar to how it's done in the join_group implementation in async_udp.cpp. This would be more robust and consistent with the runtime validation.