Add Source Specific Multicast (SSM) support

SSM can be enabled using "source" option in UDP options. Previously, "source" is considered the same as "interface", which is incorrect. 

Fixes #332.
This commit is contained in:
fatpelt 2018-03-06 11:09:28 -07:00 committed by Kongqun Yang
parent f2e9c95628
commit 6a8d2aa443
5 changed files with 90 additions and 19 deletions

View File

@ -8,11 +8,16 @@ options:
Allow or disallow reusing UDP sockets.
:interface=<addr>, source=<addr>:
:interface=<addr>:
Multicast group interface address. Only the packets sent to this address is
Multicast group interface address. Only the packets sent to this address are
received. Default to "0.0.0.0" if not specified.
:source=<addr>:
Multicast source ip address. Only the packets sent from this source address
are received. Defaults to "0.0.0.0" if not specified.
:timeout=<microseconds>:
UDP timeout in microseconds.

View File

@ -204,22 +204,54 @@ bool UdpFile::Open() {
}
if (is_multicast) {
struct ip_mreq multicast_group;
multicast_group.imr_multiaddr = local_in_addr;
if (options->is_source_specific_multicast()) {
struct ip_mreq_source source_multicast_group;
if (inet_pton(AF_INET, options->interface_address().c_str(),
&multicast_group.imr_interface) != 1) {
LOG(ERROR) << "Malformed IPv4 interface address "
<< options->interface_address();
return false;
}
source_multicast_group.imr_multiaddr = local_in_addr;
if (inet_pton(AF_INET,
options->interface_address().c_str(),
&source_multicast_group.imr_interface) != 1) {
LOG(ERROR) << "Malformed IPv4 interface address "
<< options->interface_address();
return false;
}
if (inet_pton(AF_INET,
options->source_address().c_str(),
&source_multicast_group.imr_sourceaddr) != 1) {
LOG(ERROR) << "Malformed IPv4 source specific multicast address "
<< options->source_address();
return false;
}
if (setsockopt(new_socket.get(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
reinterpret_cast<const char*>(&multicast_group),
sizeof(multicast_group)) < 0) {
LOG(ERROR) << "Failed to join multicast group.";
return false;
}
if (setsockopt(new_socket.get(),
IPPROTO_IP,
IP_ADD_SOURCE_MEMBERSHIP,
reinterpret_cast<const char*>(&source_multicast_group),
sizeof(source_multicast_group)) < 0) {
LOG(ERROR) << "Failed to join multicast group.";
return false;
}
} else {
// this is a v2 join without a specific source.
struct ip_mreq multicast_group;
multicast_group.imr_multiaddr = local_in_addr;
if (inet_pton(AF_INET, options->interface_address().c_str(),
&multicast_group.imr_interface) != 1) {
LOG(ERROR) << "Malformed IPv4 interface address "
<< options->interface_address();
return false;
}
if (setsockopt(new_socket.get(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
reinterpret_cast<const char*>(&multicast_group),
sizeof(multicast_group)) < 0) {
LOG(ERROR) << "Failed to join multicast group.";
return false;
}
}
#if defined(__linux__)
// Disable IP_MULTICAST_ALL to avoid interference caused when two sockets

View File

@ -24,6 +24,7 @@ enum FieldType {
kUnknownField = 0,
kReuseField,
kInterfaceAddressField,
kMulticastSourceField,
kTimeoutField,
};
@ -35,7 +36,7 @@ struct FieldNameToTypeMapping {
const FieldNameToTypeMapping kFieldNameTypeMappings[] = {
{"reuse", kReuseField},
{"interface", kInterfaceAddressField},
{"source", kInterfaceAddressField},
{"source", kMulticastSourceField},
{"timeout", kTimeoutField},
};
@ -105,6 +106,10 @@ std::unique_ptr<UdpOptions> UdpOptions::ParseFromString(
return nullptr;
}
break;
case kMulticastSourceField:
options->source_address_ = pair.second;
options->is_source_specific_multicast_ = true;
break;
default:
LOG(ERROR) << "Unknown field in udp options (\"" << pair.first
<< "\").";

View File

@ -26,6 +26,10 @@ class UdpOptions {
bool reuse() const { return reuse_; }
const std::string& interface_address() const { return interface_address_; }
unsigned timeout_us() const { return timeout_us_; }
const std::string& source_address() const { return source_address_; }
bool is_source_specific_multicast() const {
return is_source_specific_multicast_;
}
private:
UdpOptions() = default;
@ -39,6 +43,9 @@ class UdpOptions {
std::string interface_address_ = "0.0.0.0";
/// Timeout in microseconds. 0 to indicate unlimited timeout.
unsigned timeout_us_ = 0;
// Source specific multicast source address
std::string source_address_ = "0.0.0.0";
bool is_source_specific_multicast_ = false;
};
} // namespace shaka

View File

@ -27,6 +27,8 @@ TEST_F(UdpOptionsTest, AddressAndPort) {
EXPECT_FALSE(options->reuse());
EXPECT_EQ("0.0.0.0", options->interface_address());
EXPECT_EQ(0u, options->timeout_us());
EXPECT_FALSE(options->is_source_specific_multicast());
EXPECT_EQ("0.0.0.0", options->source_address());
}
TEST_F(UdpOptionsTest, MissingPort) {
@ -55,6 +57,8 @@ TEST_F(UdpOptionsTest, UdpInterfaceAddressFlag) {
EXPECT_FALSE(options->reuse());
EXPECT_EQ("10.11.12.13", options->interface_address());
EXPECT_EQ(0u, options->timeout_us());
EXPECT_FALSE(options->is_source_specific_multicast());
EXPECT_EQ("0.0.0.0", options->source_address());
}
TEST_F(UdpOptionsTest, Reuse) {
@ -64,6 +68,8 @@ TEST_F(UdpOptionsTest, Reuse) {
EXPECT_TRUE(options->reuse());
EXPECT_EQ("0.0.0.0", options->interface_address());
EXPECT_EQ(0u, options->timeout_us());
EXPECT_FALSE(options->is_source_specific_multicast());
EXPECT_EQ("0.0.0.0", options->source_address());
}
TEST_F(UdpOptionsTest, InvalidReuse) {
@ -78,21 +84,37 @@ TEST_F(UdpOptionsTest, InterfaceAddress) {
EXPECT_FALSE(options->reuse());
EXPECT_EQ("10.11.12.13", options->interface_address());
EXPECT_EQ(0u, options->timeout_us());
EXPECT_FALSE(options->is_source_specific_multicast());
EXPECT_EQ("0.0.0.0", options->source_address());
}
TEST_F(UdpOptionsTest, SourceAddress) {
auto options = UdpOptions::ParseFromString(
"224.1.2.30:88?interface=10.11.12.13&source=10.14.15.16");
EXPECT_EQ("224.1.2.30", options->address());
EXPECT_EQ(88u, options->port());
EXPECT_FALSE(options->reuse());
EXPECT_EQ("10.11.12.13", options->interface_address());
EXPECT_EQ(0u, options->timeout_us());
EXPECT_TRUE(options->is_source_specific_multicast());
EXPECT_EQ("10.14.15.16", options->source_address());
}
TEST_F(UdpOptionsTest, Timeout) {
auto options = UdpOptions::ParseFromString(
"224.1.2.30:88?source=10.11.12.13&timeout=88888888");
"224.1.2.30:88?interface=10.11.12.13&timeout=88888888");
EXPECT_EQ("224.1.2.30", options->address());
EXPECT_EQ(88u, options->port());
EXPECT_FALSE(options->reuse());
EXPECT_EQ("10.11.12.13", options->interface_address());
EXPECT_EQ(88888888u, options->timeout_us());
EXPECT_FALSE(options->is_source_specific_multicast());
EXPECT_EQ("0.0.0.0", options->source_address());
}
TEST_F(UdpOptionsTest, InvalidTimeout) {
ASSERT_FALSE(UdpOptions::ParseFromString(
"224.1.2.30:88?source=10.11.12.13&timeout=1a9"));
"224.1.2.30:88?interface=10.11.12.13&timeout=1a9"));
}
} // namespace shaka