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:
parent
f2e9c95628
commit
6a8d2aa443
|
@ -8,11 +8,16 @@ options:
|
||||||
|
|
||||||
Allow or disallow reusing UDP sockets.
|
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.
|
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>:
|
:timeout=<microseconds>:
|
||||||
|
|
||||||
UDP timeout in microseconds.
|
UDP timeout in microseconds.
|
||||||
|
|
|
@ -204,7 +204,37 @@ bool UdpFile::Open() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_multicast) {
|
if (is_multicast) {
|
||||||
|
if (options->is_source_specific_multicast()) {
|
||||||
|
struct ip_mreq_source source_multicast_group;
|
||||||
|
|
||||||
|
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_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;
|
struct ip_mreq multicast_group;
|
||||||
|
|
||||||
multicast_group.imr_multiaddr = local_in_addr;
|
multicast_group.imr_multiaddr = local_in_addr;
|
||||||
|
|
||||||
if (inet_pton(AF_INET, options->interface_address().c_str(),
|
if (inet_pton(AF_INET, options->interface_address().c_str(),
|
||||||
|
@ -221,6 +251,8 @@ bool UdpFile::Open() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
// Disable IP_MULTICAST_ALL to avoid interference caused when two sockets
|
// Disable IP_MULTICAST_ALL to avoid interference caused when two sockets
|
||||||
// are bound to the same port but joined to different multicast groups.
|
// are bound to the same port but joined to different multicast groups.
|
||||||
|
|
|
@ -24,6 +24,7 @@ enum FieldType {
|
||||||
kUnknownField = 0,
|
kUnknownField = 0,
|
||||||
kReuseField,
|
kReuseField,
|
||||||
kInterfaceAddressField,
|
kInterfaceAddressField,
|
||||||
|
kMulticastSourceField,
|
||||||
kTimeoutField,
|
kTimeoutField,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,7 +36,7 @@ struct FieldNameToTypeMapping {
|
||||||
const FieldNameToTypeMapping kFieldNameTypeMappings[] = {
|
const FieldNameToTypeMapping kFieldNameTypeMappings[] = {
|
||||||
{"reuse", kReuseField},
|
{"reuse", kReuseField},
|
||||||
{"interface", kInterfaceAddressField},
|
{"interface", kInterfaceAddressField},
|
||||||
{"source", kInterfaceAddressField},
|
{"source", kMulticastSourceField},
|
||||||
{"timeout", kTimeoutField},
|
{"timeout", kTimeoutField},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -105,6 +106,10 @@ std::unique_ptr<UdpOptions> UdpOptions::ParseFromString(
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case kMulticastSourceField:
|
||||||
|
options->source_address_ = pair.second;
|
||||||
|
options->is_source_specific_multicast_ = true;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
LOG(ERROR) << "Unknown field in udp options (\"" << pair.first
|
LOG(ERROR) << "Unknown field in udp options (\"" << pair.first
|
||||||
<< "\").";
|
<< "\").";
|
||||||
|
|
|
@ -26,6 +26,10 @@ class UdpOptions {
|
||||||
bool reuse() const { return reuse_; }
|
bool reuse() const { return reuse_; }
|
||||||
const std::string& interface_address() const { return interface_address_; }
|
const std::string& interface_address() const { return interface_address_; }
|
||||||
unsigned timeout_us() const { return timeout_us_; }
|
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:
|
private:
|
||||||
UdpOptions() = default;
|
UdpOptions() = default;
|
||||||
|
@ -39,6 +43,9 @@ class UdpOptions {
|
||||||
std::string interface_address_ = "0.0.0.0";
|
std::string interface_address_ = "0.0.0.0";
|
||||||
/// Timeout in microseconds. 0 to indicate unlimited timeout.
|
/// Timeout in microseconds. 0 to indicate unlimited timeout.
|
||||||
unsigned timeout_us_ = 0;
|
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
|
} // namespace shaka
|
||||||
|
|
|
@ -27,6 +27,8 @@ TEST_F(UdpOptionsTest, AddressAndPort) {
|
||||||
EXPECT_FALSE(options->reuse());
|
EXPECT_FALSE(options->reuse());
|
||||||
EXPECT_EQ("0.0.0.0", options->interface_address());
|
EXPECT_EQ("0.0.0.0", options->interface_address());
|
||||||
EXPECT_EQ(0u, options->timeout_us());
|
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) {
|
TEST_F(UdpOptionsTest, MissingPort) {
|
||||||
|
@ -55,6 +57,8 @@ TEST_F(UdpOptionsTest, UdpInterfaceAddressFlag) {
|
||||||
EXPECT_FALSE(options->reuse());
|
EXPECT_FALSE(options->reuse());
|
||||||
EXPECT_EQ("10.11.12.13", options->interface_address());
|
EXPECT_EQ("10.11.12.13", options->interface_address());
|
||||||
EXPECT_EQ(0u, options->timeout_us());
|
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) {
|
TEST_F(UdpOptionsTest, Reuse) {
|
||||||
|
@ -64,6 +68,8 @@ TEST_F(UdpOptionsTest, Reuse) {
|
||||||
EXPECT_TRUE(options->reuse());
|
EXPECT_TRUE(options->reuse());
|
||||||
EXPECT_EQ("0.0.0.0", options->interface_address());
|
EXPECT_EQ("0.0.0.0", options->interface_address());
|
||||||
EXPECT_EQ(0u, options->timeout_us());
|
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) {
|
TEST_F(UdpOptionsTest, InvalidReuse) {
|
||||||
|
@ -78,21 +84,37 @@ TEST_F(UdpOptionsTest, InterfaceAddress) {
|
||||||
EXPECT_FALSE(options->reuse());
|
EXPECT_FALSE(options->reuse());
|
||||||
EXPECT_EQ("10.11.12.13", options->interface_address());
|
EXPECT_EQ("10.11.12.13", options->interface_address());
|
||||||
EXPECT_EQ(0u, options->timeout_us());
|
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) {
|
TEST_F(UdpOptionsTest, Timeout) {
|
||||||
auto options = UdpOptions::ParseFromString(
|
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("224.1.2.30", options->address());
|
||||||
EXPECT_EQ(88u, options->port());
|
EXPECT_EQ(88u, options->port());
|
||||||
EXPECT_FALSE(options->reuse());
|
EXPECT_FALSE(options->reuse());
|
||||||
EXPECT_EQ("10.11.12.13", options->interface_address());
|
EXPECT_EQ("10.11.12.13", options->interface_address());
|
||||||
EXPECT_EQ(88888888u, options->timeout_us());
|
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) {
|
TEST_F(UdpOptionsTest, InvalidTimeout) {
|
||||||
ASSERT_FALSE(UdpOptions::ParseFromString(
|
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
|
} // namespace shaka
|
||||||
|
|
Loading…
Reference in New Issue