thanks @vk...@google.com for guidance. i've reduce the scope here to net/socket layer and moved the rest to CL7186747
Add Source-Specific Multicast (SSM) support to Direct Sockets APIOmar RamadanAttach link to chrome entry for other reviewers.
Done
TEST_F(UDPSocketTest, JoinSourceGroupIPv4) {Omar RamadanI would split the CL into multiple. The /net/socket/ directory should be in a separate CL from other layers to simplify job of the reviewers.
Done
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// Result codes for Direct Sockets operations.Omar Ramadanunused?
Done
public:Omar RamadanIf blink layer is modified, then it must be tested.
First of all unit tests: third_party/blink/renderer/modules/direct_sockets/multicast_controller_unittest.ccSecond, e2e tests on chrome or content layer:
content/browser/direct_sockets/direct_sockets_udp_browsertest.cc,
chrome/browser/direct_sockets/direct_sockets_apitest.ccThere should be complete cases, checking that source filtering works, and many other corner cases.
addressed in CL7186747
struct ip_mreq_source mreqs = {};Can you define helper functions to populate a `struct ip_mreq_source` or `struct group_source_req` from an `IPAddress` that we can use in both the join and leave methods?
std::memcpy(&group->sin6_addr, group_address.bytes().data(),Why `memcpy` here instead of using `ToIn6Addr`?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Can you define helper functions to populate a `struct ip_mreq_source` or `struct group_source_req` from an `IPAddress` that we can use in both the join and leave methods?
added `CreateIPv4SourceGroupRequest` / `CreateIPv6SourceGroupRequest` helpers
std::memcpy(&group->sin6_addr, group_address.bytes().data(),Why `memcpy` here instead of using `ToIn6Addr`?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
struct ip_mreq_source mreqs;Add struct building helpers here as well?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Add struct building helpers here as well?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
| Code-Review | +0 |
please include /net owners.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Mostly looking good! Next time, please consider limiting the number of reviewers.
https://chromium.googlesource.com/chromium/src/+/lkgr/docs/cl_respect.md#choose-your-reviewers
I think you need two approvals from chromium committers though.
// |group_address| is the multicast group address to join.nit: We use backquotes for new code even existing code uses bars.
https://chromium.googlesource.com/chromium/src/+/HEAD/styleguide/c++/c++-dos-and-donts.md#comment-style
// Validate that both addresses are the same IP versionnit: Please add period.
// Tests for Source-Specific Multicast (SSM)Can we also have tests to cover not connected cases?
thanks @ba...@chromium.org for the review and guidance. first time contributor so apologies for the large number of reviewers tagged
// |group_address| is the multicast group address to join.nit: We use backquotes for new code even existing code uses bars.
https://chromium.googlesource.com/chromium/src/+/HEAD/styleguide/c++/c++-dos-and-donts.md#comment-style
Done
// Validate that both addresses are the same IP versionOmar Ramadannit: Please add period.
Done
Can we also have tests to cover not connected cases?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
looking good, running trybots before approval.
thanks -- keeping an eye on the test run results
Can you fix the test failure? Thanks!
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7160820You don't need to add the Reviewed-on field yourself, Gerrit does it automatically when the change is submitted.
I suggest you define a constant like
```
#if defined(MCAST_LEAVE_SOURCE_GROUP) && !BUILDFLAG(IS_ANDROID) && \
!BUILDFLAG(IS_IOS)
constexpr bool kExpectSSMToWork = true;
#else
constexpr bool kExpectSSMToWork = false;
#endif
```
and then use `kExpectSSMToWork` in the tests to ensure that ERR_NOT_IMPLEMENTED is only returned when we expect it to be.
// If either failed, platform doesn't support SSMThey may have failed for some other reason, this test is not reliable.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
No worries at all, thank you for the thorough review!
Changes:
1. Removed Reviewed-on: field from commit message - Gerrit will add this automatically on submit.
2. Added kExpectSSMToWork constant for reliable test behavior:
#if defined(MCAST_JOIN_SOURCE_GROUP) && !BUILDFLAG(IS_ANDROID) && \
!BUILDFLAG(IS_IOS)
constexpr bool kExpectSSMToWork = true;
#else
constexpr bool kExpectSSMToWork = false;
#endif
3. Updated SSM tests to use kExpectSSMToWork:
- JoinSourceGroupIPv4/JoinSourceGroupIPv6: Assert IsOk() when SSM is expected to work, ERR_NOT_IMPLEMENTED otherwise
- JoinSourceGroupMultipleSources/JoinSourceGroupMultipleSourcesIPv6: Use GTEST_SKIP() when SSM not supported; assert all operations succeed when it is (removed unreliable "if failed, platform doesn't support SSM" pattern)
- SSMSourceFilteringMultiNIC: Added kExpectSSMToWork skip check
Tested locally on Linux/OSX/Windows.
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7160820You don't need to add the Reviewed-on field yourself, Gerrit does it automatically when the change is submitted.
I suggest you define a constant like
```
#if defined(MCAST_LEAVE_SOURCE_GROUP) && !BUILDFLAG(IS_ANDROID) && \
!BUILDFLAG(IS_IOS)
constexpr bool kExpectSSMToWork = true;
#else
constexpr bool kExpectSSMToWork = false;
#endif
```
and then use `kExpectSSMToWork` in the tests to ensure that ERR_NOT_IMPLEMENTED is only returned when we expect it to be.
Done
They may have failed for some other reason, this test is not reliable.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// Joins a source-specific multicast group. Returns a net error code.Why did you remove the extra information from the comment? The implementation details might have been unnecessary, but the description of parameters and reference to the RFC were useful.
uint32_t GetInterfaceViaRoute(const IPAddress& source_address) {"GetInterfaceForDestination()" might be a better name.
int sock = socket(family, SOCK_DGRAM, 0);You should locally create a `net::UDPSocketPosix` object instead of doing OS calls directly.
if (getifaddrs(&ifap) != 0) {Use `net::GetNetworkList(&networks, INCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES)` instead of calling `getifaddrs()` directly.
// Validate that both addresses are the same IP version.Why did you remove this comment? I found it useful.
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)This whole chunk of code seems to be identical to a chunk in LeaveSourceGroup(), with the only difference being whether we pass `MCAST_JOIN_SOURCE_GROUP` or `MCAST_LEAVE_SOURCE_GROUP`. So you can factor it out into a method that takes the option as an argument, and call the new method from both places.
if (!iface.address.IsLoopback()) {Loopback is already skipped by GetNetworkList(), so this check is unnecessary. Which means you can avoid the loop and just `return interfaces.size() >= 2`.
if (group_address.IsIPv4()) {You can reduce code duplication here by defining a function
`CreateSourceGroupRequest()` which calls one of `CreateIPv4SourceGroupRequest()` or `CreateIPv6SourceGroupRequest()` depending on the value of `group_address.IsIPv4()`. Then the only difference between the IPv4 case and the IPv6 case is whether you pass `IPPROTO_IP` or `IPPROTO_IPV6` to `setsocketopt()`, which you can just write like `group_address.IsIPv4() ? IPPROTO_IP : IPPROTO_IPV6`.
You can also factor out the common code with LeaveSourceGroup() as I described for the POSIX version of the code.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// Joins a source-specific multicast group. Returns a net error code.Why did you remove the extra information from the comment? The implementation details might have been unnecessary, but the description of parameters and reference to the RFC were useful.
Done
uint32_t GetInterfaceViaRoute(const IPAddress& source_address) {"GetInterfaceForDestination()" might be a better name.
Done
You should locally create a `net::UDPSocketPosix` object instead of doing OS calls directly.
Done
Use `net::GetNetworkList(&networks, INCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES)` instead of calling `getifaddrs()` directly.
Done
// Validate that both addresses are the same IP version.Why did you remove this comment? I found it useful.
Done
This whole chunk of code seems to be identical to a chunk in LeaveSourceGroup(), with the only difference being whether we pass `MCAST_JOIN_SOURCE_GROUP` or `MCAST_LEAVE_SOURCE_GROUP`. So you can factor it out into a method that takes the option as an argument, and call the new method from both places.
Done
Loopback is already skipped by GetNetworkList(), so this check is unnecessary. Which means you can avoid the loop and just `return interfaces.size() >= 2`.
Done
You can reduce code duplication here by defining a function
`CreateSourceGroupRequest()` which calls one of `CreateIPv4SourceGroupRequest()` or `CreateIPv6SourceGroupRequest()` depending on the value of `group_address.IsIPv4()`. Then the only difference between the IPv4 case and the IPv6 case is whether you pass `IPPROTO_IP` or `IPPROTO_IPV6` to `setsocketopt()`, which you can just write like `group_address.IsIPv4() ? IPPROTO_IP : IPPROTO_IPV6`.You can also factor out the common code with LeaveSourceGroup() as I described for the POSIX version of the code.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// |group_address| must be in the SSM range (232.0.0.0/8 for IPv4 orNit: please use backticks rather than pipes for variable names in comments in new code. Also below.
int sock = socket(family, SOCK_DGRAM, 0);Omar RamadanYou should locally create a `net::UDPSocketPosix` object instead of doing OS calls directly.
Done
Please do not reply "Done" when you haven't actually done what was asked for.
If you are going to use `socket()` directly, you should add least use `base::ScopedFD` so that no file descriptor leaks are introduced.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// |group_address| must be in the SSM range (232.0.0.0/8 for IPv4 orNit: please use backticks rather than pipes for variable names in comments in new code. Also below.
Done
int sock = socket(family, SOCK_DGRAM, 0);Omar RamadanYou should locally create a `net::UDPSocketPosix` object instead of doing OS calls directly.
Adam RiceDone
Please do not reply "Done" when you haven't actually done what was asked for.
If you are going to use `socket()` directly, you should add least use `base::ScopedFD` so that no file descriptor leaks are introduced.
Appologies Adam on the oversight. Thanks for the tip for `base::ScopedFD` using `net::UDPSocketPosix` is overkill for routing info we need
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
for (const auto& iface : interfaces) {
if ((family == AF_INET && iface.address.IsIPv4()) ||
(family == AF_INET6 && iface.address.IsIPv6())) {
return iface.interface_index;
}
}Maybe more efficient:
```suggestion
for (const auto& iface : interfaces) {
// If the size matches, then the family must match.
if (destination_address.size() == iface.address.size()) {
return iface.interface_index;
}
}
```
for (const auto& iface : interfaces) {
if ((family == AF_INET && iface.address.IsIPv4()) ||
(family == AF_INET6 && iface.address.IsIPv6())) {
return iface.interface_index;
}
}Maybe more efficient:
```suggestion
for (const auto& iface : interfaces) {
// If the size matches, then the family must match.
if (destination_address.size() == iface.address.size()) {
return iface.interface_index;
}
}
```
This fallback for macOS testing didn't end up working as kernel returns `ENOMEM` when joining a second source for the same (group, interface) pair with an explicit interface index.
Sequence with documentation addresses:
1. `JoinSourceGroup(232.1.1.1, 192.0.2.1)` → `connect()` fails → fallback returns interface 5 → succeeds
2. `JoinSourceGroup(232.1.1.1, 192.0.2.2)` → `connect()` fails → fallback returns interface 5 → ENOMEM
With real local addresses (from `GetLocalAddresses()`):
1. `JoinSourceGroup(232.1.1.1, 10.0.0.50)` → `connect()` succeeds → returns interface via routing → succeeds
2. `JoinSourceGroup(232.1.1.1, 192.168.1.100)` → `connect()` succeeds → may return same or different interface → succeeds
The difference seems to be that when macOS can actually route to the source address (even if it's a local address), the kernel handles multiple source subscriptions correctly. With non-routable addresses and an explicit fallback interface, something in the kernel's SSM state management fails.
While it would work to update the test cases to use an internet routable pair of IPs e.g 8.8.8 and 8.8.4.4, adding a `GetLocalAddresses` helper for MacOS to retrieve available test runner address pairs
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
lgtm, thanks.
#if BUILDFLAG(IS_MAC)Nit: since this code is compilable on other platforms, it is better to do
```
if constexpr (BUILDFLAG(IS_MAC)) {
...
} else {
...
}
```
This will make it easier for non-macOS developers to update both branches if they need to. The unused branch is checked for correctness but not compiled into the binary.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
thanks for the +1. addressing nits and MacOS unroutable address test failures for v6
Nit: since this code is compilable on other platforms, it is better to do
```
if constexpr (BUILDFLAG(IS_MAC)) {
...
} else {
...
}
```
This will make it easier for non-macOS developers to update both branches if they need to. The unused branch is checked for correctness but not compiled into the binary.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
seems macOS CI machines only have link-local addresses - adding skip if no suitable IPs
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
any additional feedback here? can i get +1
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
reillyg, if you are okay with this now, I will submit.
| Code-Review | +1 |
LGTM
reillyg, if you are okay with this now, I will submit.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
Please fix or disable this feature on Fuchsia.
Please fix or disable this feature on Fuchsia.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
| Commit-Queue | +1 |
LGTM
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +1 |
Add Source-Specific Multicast (SSM) support to net/socket layer
https://chromestatus.com/feature/6208452397498368
Intent to prototype: https://groups.google.com/a/chromium.org/d/msgid/blink-dev/692dffe2.050a0220.17ec37.0673.GAE%40google.com
Implement low-level socket operations for Source-Specific Multicast
(SSM) to enable OS-level source filtering when joining multicast groups,
enabling source-specific multicast filtering for improved security,
particularly outside private networks.
Changes:
- Add UDPSocket::JoinSourceGroup() using IP_ADD_SOURCE_MEMBERSHIP (POSIX)
- Add UDPSocket::LeaveSourceGroup() using IP_DROP_SOURCE_MEMBERSHIP (POSIX)
- Add IPv6 SSM support via MCAST_JOIN_SOURCE_GROUP / MCAST_LEAVE_SOURCE_GROUP
- Handle Apple / BSD group_source_req struct variant address length field
- Add comprehensive unit tests for IPv4 and IPv6 SSM operations
Unit Tests:
- Nominal SSM Join + Leave for IPv4 and IPv6
- Nominal SSM Join + Leave for multiple sources
- Error on Leave SSM group not joined
- Error on IP version mismatch between source and group
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |