-
Notifications
You must be signed in to change notification settings - Fork 3k
Add multicast implementation for UDPSocket #4820
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
Conversation
@kegilbert Please put only one of those tags on at a time. |
cc @geky |
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.
Looks good so far, several nits and comments, but it looks like it's coming along.
Also ccing @kjbracey-arm
features/netsocket/nsapi_types.h
Outdated
typedef struct nsapi_ip_mreq | ||
{ | ||
nsapi_addr_t imr_multiaddr; /* IP multicast address of group */ | ||
uint32_t imr_interface; /* local IP address of interface */ |
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.
@kegilbert, shouldn't imr_interface
also be a nsapi_addr_t
?
features/netsocket/nsapi_types.h
Outdated
/** nsapi_ip_mreq structure | ||
*/ | ||
typedef struct nsapi_ip_mreq | ||
{ |
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.
nit: this should match the style of the surrounding source (braces)
features/netsocket/UDPSocket.cpp
Outdated
|
||
// Set up group address | ||
mreq.imr_multiaddr = address->get_addr(); | ||
mreq.imr_interface = ((uint32_t)0x00000000UL); |
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.
Could this be
mreq.imr_interface = SocketAddress().get_addr();
if (optname == NSAPI_ADD_MEMBERSHIP) { | ||
igmp_err = igmp_joingroup(&if_addr, &multi_addr); | ||
} else { | ||
igmp_err = igmp_leavegroup(&if_addr, &multi_addr); |
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.
small nit: the tabbing here looks a bit off
features/netsocket/UDPSocket.h
Outdated
@@ -56,6 +56,8 @@ class UDPSocket : public Socket { | |||
*/ | |||
virtual ~UDPSocket(); | |||
|
|||
int join_multicast_group(SocketAddress* address); |
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.
Needs documentation?
Also SocketAddress is a first class object, so it should be passed around by value or reference. The should be
int join_multicast_group(const SocketAddress &address);
That way you can pass implicitly cast strings
TCPSocket s
s.join_multicast_group("250.0.0.10");
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.
Looks good to me 👍
@geky mbed OS 2 used an array in the lwip_socket layer to keep track of multicast sockets. Besides the register and unregister membership function which added/removed sockets from the array, I can't find any other references to indicate who used that list of sockets. That implementation is currently missing from this PR. I mentioned it in the original comment, so just wanted to address that. If you or anyone else knows more about where that list was used I can add it to the ADD/DROP_MEMBERSHIP case otherwise I'll leave it as is. |
Hmm, I can't really find any consumers for that array. My guess is it's old cruft from a previous port of lwip. It's not currently in mbed OS so it's probably fine to move forward. |
NOTE This implementation is not designed for IPv6. There was a change in how multicast works in IPv6 and has not yet been fully tested. Will move any IPv6 work that may be needed to another PR to keep this one minimal. |
Please rebase, to resolve travis failure. Is this ready then for review? |
@0xc0170 Rebased. It is ready for review. |
@kjbracey-arm Can you please review? |
inet_addr_convert.s_addr = multiaddr_ip.addr; | ||
|
||
#if LWIP_IPV4 | ||
// LWIP inet IP represnetation (in_addr) to ip_addr_t, both structs containing |
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.
representation typo in the comment
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.
Woops, good catch thanks!
features/netsocket/UDPSocket.cpp
Outdated
@@ -33,6 +33,16 @@ nsapi_protocol_t UDPSocket::get_proto() | |||
return NSAPI_UDP; | |||
} | |||
|
|||
int UDPSocket::join_multicast_group(const SocketAddress &address) { |
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.
follow the style that is in this file (codebase) {
on the new line
int UDPSocket::join_multicast_group(const SocketAddress &address)
{
@kjbracey-arm @0xc0170 Was there anything you wanted me to take care of for this review? |
Someone from networking team to review besides Chris (thx for review 👍 ). @mikaleppanen @hasnainvirk I assume kevin is still not back, can you or tag someone who could review this? |
I'm back today. I'll leave a few comments. |
*/ | ||
typedef struct nsapi_ip_mreq { | ||
nsapi_addr_t imr_multiaddr; /* IP multicast address of group */ | ||
nsapi_addr_t imr_interface; /* local IP address of interface */ |
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 standard ipv6_mreq
uses an interface ID rather than an address to indirectly name it.
If this structure is intended for use by both IPv4 and IPv6, could we use an actual NetworkInterface * pointer instead (given that we don't have integer interface IDs)? Easier to get from the interface to the address if needed than the other way around.
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.
This PR is aiming to add IPv4 support only. I can look into changing this to make the transition to IPv6 support easier if you'd rather get it down now but left as is for the moment.
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 current implementation supports IPv4 and IPv6 equally. I don't think a PR that leaves IPv6 support less functional than IPv4 support is acceptable. Regardless, if you're adding IPv4 something you need to think what the IPv6 will look like.
@@ -1023,6 +1043,36 @@ static nsapi_error_t mbed_lwip_setsockopt(nsapi_stack_t *stack, nsapi_socket_t h | |||
} | |||
return 0; | |||
|
|||
case NSAPI_ADD_MEMBERSHIP: | |||
case NSAPI_DROP_MEMBERSHIP: { | |||
#if LWIP_IPV6 |
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.
This flag is not an either-or, so the #if/#else
is inappropriate. Both LWIP_IPV4 and LWIP_IPV6 can be enabled.
If this option will be for IPv4 only, then just wrap the whole case in #if LWIP_IPv4
.
If this option is for either IPv4 or 6, then you would need to have a bit of messing around to check the address type, with a bunch of separate `#if's. Search the code for examples.
NSAPI_LINGER, /*!< Keeps close from returning until queues empty */ | ||
NSAPI_SNDBUF, /*!< Sets send buffer size */ | ||
NSAPI_RCVBUF, /*!< Sets recv buffer size */ | ||
NSAPI_ADD_MEMBERSHIP, /*!< Add membership to multicast address */ |
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.
I'm beginning to hurt a little about the lack of prefixes or indeed proper levels here. The earlier ones should all have been TCP_
. Can these at least be IP_
?
Do we think we use a single socket option for both IPv4 and IPv6, as the nsapi_addr_t inside the mreq contains a version number? Is that your intent here? (Systems normally have separate IP_ADD_MEMBERSHIP
and IPV6_JOIN_GROUP
/IPV6_ADD_MEMBERSHIP
.)
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.
This PR is currently only for IPv4 support. This would be a good point for the IPv6 support PR.
I can change it to IP_ if you feel strongly for it, but my preference is to keep new additions consistent with our current naming if we can't modify all of them to TCP_/IP_.
features/netsocket/UDPSocket.cpp
Outdated
mreq.imr_multiaddr = address.get_addr(); | ||
mreq.imr_interface = SocketAddress().get_addr(); // 0.0.0.0 INADDR_ANY | ||
|
||
return this->setsockopt(0, NSAPI_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); |
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.
_stack
as first parameter, no?
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.
This calls into Socket's setsockopt function, first param is level:
See here
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.
My mistake. In that case, it should be NSAPI_SOCKET (indicating that it's a generic NSAPI option, rather than a stack-specific one that should be passed through).
lwIP has no "native" socket options, so its adaptation layer gets away with ignoring the level parameter, but other stacks will need that.
features/netsocket/UDPSocket.cpp
Outdated
@@ -33,6 +33,17 @@ nsapi_protocol_t UDPSocket::get_proto() | |||
return NSAPI_UDP; | |||
} | |||
|
|||
int UDPSocket::join_multicast_group(const SocketAddress &address) |
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.
Is this needed in addition to the socket option? Having both feels kind of the worst of both worlds - the socket option switch statement has the disadvantage (in an embedded system) that it pulls in all the stack code at link time, even for cases that are never invoked. This is a neat separate API that would allow linker exclusion, but then goes through the switch anyway.
Don't feel terribly strongly, except maybe you should have the leave function to match if you have this.
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.
Not necessarily, although I prefer this style since it looks a lot cleaner to a user and mimics what we used to have.
} | ||
|
||
err_t igmp_err; | ||
nsapi_ip_mreq_t *imr = (nsapi_ip_mreq_t *)optval; |
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.
Cast not required cos it's C. Its removal will encourage you to preserve const-correctness.
} else { | ||
igmp_err = igmp_leavegroup(&if_addr, &multi_addr); | ||
} | ||
if (igmp_err != ERR_OK) { |
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.
I suggest return mbed_lwip_err_remap(igmp_err);
, if that returns normal error codes
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.
It would default to NSAPI_ERROR_DEVICE_ERROR. If that's fine I can change it.
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.
If I read correctly, the only current possible error return is ERR_VAL, which maps NSAPI_ERROR_PARAMETER.
@kjbracey-arm There was an update, can you review pls? @kegilbert It's helpful to bump this with an update description (what has changed since the last review. |
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.
Still not happy with this unless it supports IPv6 - it currently doesn't even correctly handle the various compilation possibilities for dual stack.
Added a suggested rework of the core bit that should cover IPv4 and IPv6 correctly (I hope).
@@ -980,6 +981,25 @@ static nsapi_size_or_error_t mbed_lwip_socket_recvfrom(nsapi_stack_t *stack, nsa | |||
return recv; | |||
} | |||
|
|||
static nsapi_error_t nsapi_addr_to_ip_addr(ip_addr_t *lwip_ip, nsapi_addr_t *nsapi_addr_ip) { |
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.
I can't quite figure out what this function is doing, and why you're not just calling convert_mbed_addr_to_lwip directly. It's trying to fill in only part of the destination address structure? Is it covering up lack of proper IPv4/IPv6 support?
case NSAPI_ADD_MEMBERSHIP: | ||
case NSAPI_DROP_MEMBERSHIP: { | ||
#if LWIP_IPV4 | ||
if (optlen != sizeof(nsapi_ip_mreq_t)) { |
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.
If your option structure contains nsapi_addr_t
s, then you also need a check somewhere that the addresses are IPv4.
And in that case you may as well include the code to call mld6_joingroup instead for IPv6. Having the logic to correctly detect and error when passed IPv6 addresses, and dealing with all 3 compilation modes (4 only, 6 only, 4+6) is no simpler than handling the IPv6.
I'm currently thinking we should keep the mreq structure as is, so it contains nsapi_addr_t with the type field, and then you should do the right IPv4/IPv6 thing depending on the address type indicated in the group.
nsapi_addr_to_ip_addr(&if_addr, &imr->imr_interface); | ||
|
||
if (optname == NSAPI_ADD_MEMBERSHIP) { | ||
igmp_err = igmp_joingroup(&if_addr, &multi_addr); |
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.
Don't see how this compiles if LWIP_IPV6 is on as well as LWIP_IPV4 - you need to use ip_2_ip4 to convert the ip_addr_t *
to ip_addr4_t *
.
|
||
// Set up group address | ||
mreq.imr_multiaddr = address.get_addr(); | ||
mreq.imr_interface = SocketAddress().get_addr(); // 0.0.0.0 INADDR_ANY |
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.
nsapi_addr_t()
would be simpler?
Comment is misleading - it's an "NSAPI_UNSPEC" address, rather than IPv4 0.0.0.0.
ip_addr_t multi_addr; | ||
|
||
nsapi_addr_to_ip_addr(&multi_addr, &imr->imr_multiaddr); | ||
nsapi_addr_to_ip_addr(&if_addr, &imr->imr_interface); |
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.
For full IPv4/IPv6 correctness, I think you need to make sure that if multi_addr.version is NSAPI_UNSPEC, you produce an ip_addr_t of the correct type using ip_addr_set_zero_ip4 or ip_addr_set_zero_ip6.
I think I'd restructure like:
/* Check interface address type matches group, or is unspecified */
if (imr->imr_interface.version != NSAPI_UNSPEC && imr->imr_interface.version != imr->imr_multiaddr.version) {
return NSAPI_ERROR_PARAMETER;
}
ip_addr_t if_addr;
ip_addr_t multi_addr;
/* Convert the group address */
if (!convert_mbed_addr_to_lwip(&multi_addr, &imr->imr_multiaddr)) {
return NSAPI_ERROR_PARAMETER;
}
/* Convert the interface address, or make sure it's the correct sort of "any" */
if (imr->imr_interface.version != NSAPI_UNSPEC) {
if (!convert_mbed_addr_to_lwip(&if_addr, &imr->imr_interface)) {
return NSAPI_ERROR_PARAMETER;
}
} else {
ip_addr_set_any(IP_IS_V6(if_addr), &imr->imr_interface)
}
igmp_err = NSAPI_ERROR_UNSUPPORTED;
#if LWIP_IPV4
if (IP_IS_V4(if_addr)) {
igmp_err = (NSAPI_ADD_MEMBERSHIP ? igmp_joingroup : igmp_leavegroup)
(ip_2_ip4(&if_addr), ip_2_ip4(&multi_addr));
}
#endif
#if LWIP_IPV6
if (IP_IS_V6(if_addr)) {
igmp_err = (NSAPI_ADD_MEMBERSHIP ? mld6_joingroup : mld6_leavegroup)
(ip_2_ip6(&if_addr), ip_2_ip6(&multi_addr));
}
#endif
Untested, but I think should cover all the compilation/runtime permutations of IPv4/IPv6.
cc @mikaleppanen to sanity-check my suggestions |
@kegilbert @kjbracey-arm What's going on with this PR? |
@theotherjimmy |
Closing this one then |
First pass at adding multicast support to mbed-os from previous implementations. This PR is in relation to the following issue: #3512
I have not yet added in the membership register/deregister steps, working on porting those now from lwip_socket.
Tested on a K64F with GCC_ARM over ethernet with the following setup: