udp: add source-specific multicast support#964
Conversation
| *.xcodeproj | ||
| *.xcworkspace | ||
|
|
||
| CMakeLists.txt |
|
Thanks for contributing @falanger! Mostly LGTM, left some comments. |
|
Hi, @saghul! Thanks for your time! I've fixed all the comments. |
|
Changes look good, though I'd like to add IPv6 support if possible. /cc @libuv/collaborators any other reviews? |
| optname = IP_DROP_SOURCE_MEMBERSHIP; | ||
| break; | ||
| default: | ||
| return -EINVAL; |
There was a problem hiding this comment.
I think one level of indentation is missing here.
There was a problem hiding this comment.
It's consistent with other "switch..case" instructions in this file.
There was a problem hiding this comment.
Even though the style is not consistent in the project, I would add indentation here as it's already the most commonly used format in the source and I think consistency is good. Anyway, let's see what others think.
|
@saghul sorry, I was wrong about IPv6 support (thanks, @santigimeno). |
|
@saghul @santigimeno any comments? |
| err = uv_ip6_addr(multicast_addr, 0, &mcast_addr6); | ||
| if (err) | ||
| return err; | ||
| err = uv_ip6_addr(multicast_addr, 0, &src_addr6); |
There was a problem hiding this comment.
shouldn't this be source_addr?
| if (err) | ||
| return err; | ||
|
|
||
| memset(&mreq, 0, sizeof mreq); |
There was a problem hiding this comment.
Could you use consistent parentheses with sizeof.
|
I was hoping to do some source-specific multicast in node.js (which uses libuv). This PR would introduce that capability but it is unfortunately stalled, is there any way to unblock it? It seemed like the last review identified some minor style issues but otherwise things were ok. |
| } | ||
|
|
||
| return 0; | ||
| #else |
There was a problem hiding this comment.
Can you add a comment here that mentions what #ifdef this corresponds to.
|
|
||
| .. c:function:: int uv_udp_set_source_membership(uv_udp_t* handle, const char* multicast_addr, const char* interface_addr, const char* source_addr, uv_membership membership) | ||
|
|
||
| Set membership for a source-specific multicast group |
There was a problem hiding this comment.
Missing period at the end of the line.
|
|
||
| :returns: 0 on success, or an error code < 0 on failure. | ||
|
|
||
| .. versionadded:: 1.10.0 |
There was a problem hiding this comment.
This version number needs to be updated. Currently, this would be 1.15.0 at the earliest.
|
@falanger it's been quite a while. Sorry about that. Are you still interested in pursuing this PR? |
|
@cjihrig Hi! Yes, I am interested in. I'll try to fix issues on this week. |
|
|
||
| ASSERT(r == 0); | ||
|
|
||
| r = uv_udp_set_source_membership(&client, "ff02::1", NULL, "2001:db8:a0b:12f0::1", UV_JOIN_GROUP); |
There was a problem hiding this comment.
Can the rest of the test still be run if UV_ENODEV or UV_EPROTONOSUPPORT is returned? If so, maybe the assertion should just allow them so that the remainder of the test is still executed. If that can't be done, maybe a separate test file should be created.
| ASSERT(r == 0); | ||
|
|
||
| /* join the source-specific multicast channel */ | ||
| r = uv_udp_set_source_membership(&client, "239.255.0.1", NULL, "10.50.129.200", UV_JOIN_GROUP); |
There was a problem hiding this comment.
Same comment here about finishing the rest of the test.
| if (err) { | ||
| err = uv_ip6_addr(multicast_addr, 0, &mcast_addr6); | ||
| if (err) | ||
| return err; |
There was a problem hiding this comment.
Two space indentation please.
There was a problem hiding this comment.
Sorry, I have fixed all indentation issues.
| return err; | ||
| err = uv_ip6_addr(source_addr, 0, &src_addr6); | ||
| if (err) | ||
| return err; |
|
|
||
| return 0; | ||
| #else | ||
| return -EPROTONOSUPPORT; |
| if (err) { | ||
| err = uv_ip6_addr(multicast_addr, 0, &mcast_addr6); | ||
| if (err) | ||
| return err; |
There was a problem hiding this comment.
Again, two space indentation. I'll stop commenting on it :-)
| int err; | ||
|
|
||
| memset(&mreq, 0, sizeof mreq); | ||
| memset(&mreq, 0, sizeof(mreq)); |
There was a problem hiding this comment.
Can you revert all of the unrelated style changes.
There was a problem hiding this comment.
Fixed. BTW, why the style is inconsistent in file (and project) scope?
There was a problem hiding this comment.
why the style is inconsistent in file (and project) scope?
Because we haven't agreed on and enforced automated linting. So, some stuff gets by the human eye test.
| mreq.imr_multiaddr.s_addr = multicast_addr->sin_addr.s_addr; | ||
| mreq.imr_sourceaddr.s_addr = source_addr->sin_addr.s_addr; | ||
|
|
||
| switch (membership) { |
There was a problem hiding this comment.
I would just write this as an if...else if...else. It would be several lines shorter, and there are only a couple of options. I'd also move it to the top of the function so that -EINVAL can be returned right away if necessary.
There was a problem hiding this comment.
I agree with you, but in this case functions uv_udp_set_membership and uv_udp_set_source_membership will be written in different manner. If this is OK, I would replace switch .. case with if .. else construction.
|
|
||
| return 0; | ||
| #else | ||
| return UV_EPROTONOSUPPORT; |
There was a problem hiding this comment.
I think this should be -EPROTONOSUPPORT.
There was a problem hiding this comment.
Come to think of it, I think we return ENOSYS in similar situations, but I could be wrong.
There was a problem hiding this comment.
In my opinion, ENOSYS means that function not implemented, so I think that EPROTONOSUPPORT or ENOPROTOOPT is more suitable error in this case.
There was a problem hiding this comment.
I'll defer to other @libuv/collaborators. Either way, this should be -EWHATEVER in the Unix code.
| memcpy(&mreq.gsr_group, multicast_addr, sizeof(mreq.gsr_group)); | ||
| memcpy(&mreq.gsr_source, source_addr, sizeof(mreq.gsr_source)); | ||
|
|
||
| switch (membership) { |
There was a problem hiding this comment.
Same comment as the previous switch statement.
| #include "stream-inl.h" | ||
| #include "req-inl.h" | ||
|
|
||
| #if defined(MCAST_JOIN_SOURCE_GROUP) && defined(MCAST_LEAVE_SOURCE_GROUP) |
There was a problem hiding this comment.
Would it be possible to consolidate with the same code in src/unix/udp.c?
There was a problem hiding this comment.
Yes, It's possible. Can I place this defines in uv.h header file?
| mreq.imr_multiaddr.s_addr = multicast_addr->sin_addr.s_addr; | ||
| mreq.imr_sourceaddr.s_addr = source_addr->sin_addr.s_addr; | ||
|
|
||
| switch (membership) { |
There was a problem hiding this comment.
Same comment about the switch. I'll stop repeating it now.
| memset(&mreq, 0, sizeof(mreq)); | ||
|
|
||
| if (interface_addr) { | ||
| err = uv_ip6_addr(interface_addr, 0, &addr6) |
There was a problem hiding this comment.
Missing semi-colon, causes a compile failure
| err = uv_ip6_addr(interface_addr, 0, &addr6) | ||
| if (err) | ||
| return err; | ||
| mreq.ipv6mr_interface = addr6.sin6_scope_id; |
There was a problem hiding this comment.
This fails to compile for me:
src\win\udp.c(798): error C2039: 'ipv6mr_interface': is not a member of 'group_source_req'
Do you mean mreq.gsr_interface?
There was a problem hiding this comment.
Yes, that's right. Fixed
There was a problem hiding this comment.
@LPardue please, send me a mention next time. I'll try to write tests as soon as I can - it's the last thing that needs to be done to merge this PR.
There was a problem hiding this comment.
@falanger will do, feel free to take inspiration from the SSM node tests I created in nodejs/node#15735
|
|
||
| :returns: 0 on success, or an error code < 0 on failure. | ||
|
|
||
| .. versionadded:: 1.15.0 |
| mreq.imr_multiaddr.s_addr = multicast_addr->sin_addr.s_addr; | ||
| mreq.imr_sourceaddr.s_addr = source_addr->sin_addr.s_addr; | ||
|
|
||
| switch (membership) { |
|
|
||
| memset(&mreq, 0, sizeof(mreq)); | ||
|
|
||
| if (interface_addr) { |
There was a problem hiding this comment.
One thing throughout this PR. Can you try to be more explicit in these ifs by checking for == and != to 0, NULL, etc.
|
|
||
| return 0; | ||
| #else | ||
| return UV_EPROTONOSUPPORT; |
There was a problem hiding this comment.
I'll defer to other @libuv/collaborators. Either way, this should be -EWHATEVER in the Unix code.
|
Seems like this is required to unblock a PR in Node.js. It would be great if we could get this to land some time soon therefore :-) |
|
Ping @cjihrig @saghul @bnoordhuis |
|
Unnecessary ping. It looks like there are a few unresolved review comments. |
|
@cjihrig Hi! I've resolved all issues. |
|
|
||
| :returns: 0 on success, or an error code < 0 on failure. | ||
|
|
||
| .. versionadded:: 1.16.0 |
There was a problem hiding this comment.
I guess it should be Version 1.20.0 now because the current version is already Version 1.19.1.
santigimeno
left a comment
There was a problem hiding this comment.
Was there any resolution with regards to the tests?
| struct sockaddr_in mcast_addr4; | ||
| struct sockaddr_in src_addr4; | ||
| struct sockaddr_in6 mcast_addr6; | ||
| struct sockaddr_in6 src_addr6; |
There was a problem hiding this comment.
I think you could use 2 sockaddr_storage that you could later on cast to sockaddr_in or sockaddr_in6. See: uv_udp_set_multicast_interface implementation.
There was a problem hiding this comment.
I don't think so because the uv_udp_set_source_membership function should reproduce uv_udp_set_membership functionality with additional parameter source_addr. These functions have the same behavior and they should be written in the same manner IMHO.
There was a problem hiding this comment.
OK, I see that uv_udp_set_membership is not using sockaddr_storage but it seems to me that it could and you could avoid having 2 extra sockaddr structs in the stack.
struct sockaddr_storage mcast_addr;
struct sockaddr_in* mcast_addr4;
struct sockaddr_in6* mcast_addr6;
struct sockaddr_storage src_addr;
struct sockaddr_in* src_addr4;
struct sockaddr_in6* src_addr6;
mcast_addr4 = (struct sockaddr_in*)&mcast_addr;
mcast_addr6 = (struct sockaddr_in6*)&mcast_addr;
src_addr4 = (struct sockaddr_in*)&src_addr;
src_addr6 = (struct sockaddr_in6*)&src_addr;Though it seems too verbose. Maybe using an union looks nicer. Something like this:
int uv_udp_set_source_membership(uv_udp_t* handle,
const char* multicast_addr,
const char* interface_addr,
const char* source_addr,
uv_membership membership) {
int err;
union address {
struct sockaddr_in sa_in;
struct sockaddr_in6 sa_in6;
};
union address mcast_addr;
union address src_addr;
err = uv_ip4_addr(multicast_addr, 0, &mcast_addr.sa_in);
if (err) {
err = uv_ip6_addr(multicast_addr, 0, &mcast_addr.sa_in6);
if (err)
return err;
err = uv_ip6_addr(source_addr, 0, &src_addr.sa_in6);
if (err)
return err;
return uv__udp_set_source_membership6(handle, &mcast_addr.sa_in6, interface_addr, &src_addr.sa_in6, membership);
}
err = uv_ip4_addr(source_addr, 0, &src_addr.sa_in);
if (err)
return err;
return uv__udp_set_source_membership4(handle, &mcast_addr.sa_in, interface_addr, &src_addr.sa_in, membership);
}There was a problem hiding this comment.
Ok, I'll take a look!
| struct sockaddr_in6 mcast_addr6; | ||
| struct sockaddr_in6 src_addr6; | ||
|
|
||
| int err = uv_ip4_addr(multicast_addr, 0, &mcast_addr4); |
There was a problem hiding this comment.
style: we separate variable declaration from assignment.
| struct sockaddr_in mcast_addr4; | ||
| struct sockaddr_in src_addr4; | ||
| struct sockaddr_in6 mcast_addr6; | ||
| struct sockaddr_in6 src_addr6; |
| err = uv_ip4_addr(source_addr, 0, &src_addr4); | ||
| if (err) | ||
| return err; | ||
| return uv__udp_set_source_membership4(handle, &mcast_addr4, interface_addr, &src_addr4, membership); |
There was a problem hiding this comment.
Style: long lines. Max should be 80 characters.
| err = uv_ip4_addr(source_addr, 0, &src_addr4); | ||
| if (err) | ||
| return err; | ||
| return uv__udp_set_source_membership4(handle, &mcast_addr4, interface_addr, &src_addr4, membership); |
|
Are you planning on adding tests? |
|
Sure, I plan to write tests for join/leave SSM group. Unfortunately, it not possible to write tests that receive/send a multicast packet (just like a "usual" multicast) because they require a "real" multicast network. |
|
Ping @falanger |
|
I see there was a push to the branch after my last comment but I am not sure what the status of this is @falanger |
|
Ping @falanger |
|
@santigimeno would you be willing to add some tests for this? It would really be great to unblock the Node.js PR by getting this ready and to land it. |
|
A pragmatic decision would be to just have a simple test that shows data flow between sender and receiver in a similar vein to the existing any source multicast. Full testing of source-specific multicast needs an environment more complex than what the framework provides (e.g. multiple network subnets and a router). It appears this ticket is stalled on that issue and there is lack of input from the project on what is an appropriate solution. |
|
What's the status of this lack of SSM support in node.js is turning into a major pain for a lot of projects. |
|
Superseded by #2202. |
Add RFC 4607 support for IPv4 and IPv6.