summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorikarienator@chromium.org <ikarienator@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-04-30 00:53:18 +0000
committerikarienator@chromium.org <ikarienator@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-04-30 00:53:18 +0000
commit5f01ce2a2ae37642c94d7be68f9b32423f69eddd (patch)
tree7ab6ea1cb8fcaec943a64ee4cb530a5f68a5a490
parentcd756659546ef1bebf9f939b1bba4a1dd64be552 (diff)
downloadchromium_src-5f01ce2a2ae37642c94d7be68f9b32423f69eddd.zip
chromium_src-5f01ce2a2ae37642c94d7be68f9b32423f69eddd.tar.gz
chromium_src-5f01ce2a2ae37642c94d7be68f9b32423f69eddd.tar.bz2
Introduce Multicast Socket API
Allow Chrome Apps developer to receive multicast socket packets by exposing join/leave group ability to UDP socket. Introducing: 1. chrome.socket.joinGroup / chrome.socket.leaveGroup / chrome.socket.getJoinedGroups to manipulate multicast group membership. 2. Socket permission 'udp-multicast-membership'. 3. chrome.socket.setMulticastTimeToLive / chrome.socket.setMulticastLoopbackMode to control the multicast packet sending for UDP sender. To expose the ability of manipulating multicast group membership and controlling multicast packet sending, new methods are added into network stack (net::UDPSocket class): 1. JoinGroup/LeaveGroup 2. SetMulticastTimeToLive/SetMulticastLoopbackMode To demo the ability, a demo app is created: https://github.com/GoogleChrome/chrome-app-samples/pull/92 TEST=Open the demo app at /chrome-app-samples/multicast in two machines of the same network. Open them and chat, they can talk to each other. NOTRY=true BUG=140681 Review URL: https://chromiumcodereview.appspot.com/12684008 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@197192 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/extensions/api/socket/socket_api.cc213
-rw-r--r--chrome/browser/extensions/api/socket/socket_api.h87
-rw-r--r--chrome/browser/extensions/api/socket/socket_apitest.cc12
-rw-r--r--chrome/browser/extensions/api/socket/udp_socket.cc52
-rw-r--r--chrome/browser/extensions/api/socket/udp_socket.h11
-rw-r--r--chrome/browser/extensions/api/socket/udp_socket_unittest.cc98
-rw-r--r--chrome/browser/extensions/extension_function_histogram_value.h5
-rw-r--r--chrome/common/extensions/api/socket.idl77
-rw-r--r--chrome/common/extensions/permissions/socket_permission_data.cc12
-rw-r--r--chrome/common/extensions/permissions/socket_permission_data.h4
-rw-r--r--chrome/common/extensions/permissions/socket_permission_unittest.cc19
-rw-r--r--chrome/test/data/extensions/api_test/socket/api/background.js34
-rw-r--r--chrome/test/data/extensions/api_test/socket/api/manifest.json5
-rw-r--r--chrome/test/data/extensions/api_test/socket/api/multicast.js198
-rw-r--r--content/public/common/socket_permission_request.h1
-rw-r--r--net/udp/udp_socket_libevent.cc137
-rw-r--r--net/udp/udp_socket_libevent.h48
-rw-r--r--net/udp/udp_socket_unittest.cc54
-rw-r--r--net/udp/udp_socket_win.cc134
-rw-r--r--net/udp/udp_socket_win.h46
20 files changed, 1210 insertions, 37 deletions
diff --git a/chrome/browser/extensions/api/socket/socket_api.cc b/chrome/browser/extensions/api/socket/socket_api.cc
index 108ab87..5b75fc9 100644
--- a/chrome/browser/extensions/api/socket/socket_api.cc
+++ b/chrome/browser/extensions/api/socket/socket_api.cc
@@ -4,7 +4,10 @@
#include "chrome/browser/extensions/api/socket/socket_api.h"
+#include <vector>
+
#include "base/bind.h"
+#include "base/hash_tables.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/permissions/socket_permission.h"
#include "chrome/browser/browser_process.h"
@@ -38,6 +41,10 @@ const char kPermissionError[] = "App does not have permission";
const char kNetworkListError[] = "Network lookup failed or unsupported";
const char kTCPSocketBindError[] =
"TCP socket does not support bind. For TCP server please use listen.";
+const char kMulticastSocketTypeError[] =
+ "Only UDP socket supports multicast.";
+const char kWildcardAddress[] = "*";
+const int kWildcardPort = 0;
SocketAsyncApiFunction::SocketAsyncApiFunction()
: manager_(NULL) {
@@ -159,7 +166,9 @@ void SocketDestroyFunction::Work() {
SocketConnectFunction::SocketConnectFunction()
: socket_id_(0),
- port_(0) {
+ hostname_(),
+ port_(0),
+ socket_(NULL) {
}
SocketConnectFunction::~SocketConnectFunction() {
@@ -476,7 +485,8 @@ SocketSendToFunction::SocketSendToFunction()
: socket_id_(0),
io_buffer_(NULL),
io_buffer_size_(0),
- port_(0) {
+ port_(0),
+ socket_(NULL) {
}
SocketSendToFunction::~SocketSendToFunction() {}
@@ -680,4 +690,203 @@ void SocketGetNetworkListFunction::SendResponseOnUIThread(
SendResponse(true);
}
+SocketJoinGroupFunction::SocketJoinGroupFunction()
+ : params_(NULL) {}
+
+SocketJoinGroupFunction::~SocketJoinGroupFunction() {}
+
+bool SocketJoinGroupFunction::Prepare() {
+ params_ = api::socket::JoinGroup::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketJoinGroupFunction::Work() {
+ int result = -1;
+ Socket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(Value::CreateIntegerValue(result));
+ return;
+ }
+
+ if (socket->GetSocketType() != Socket::TYPE_UDP) {
+ error_ = kMulticastSocketTypeError;
+ SetResult(Value::CreateIntegerValue(result));
+ return;
+ }
+
+ SocketPermission::CheckParam param(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
+ kWildcardAddress,
+ kWildcardPort);
+
+ if (!GetExtension()->CheckAPIPermissionWithParam(APIPermission::kSocket,
+ &param)) {
+ error_ = kPermissionError;
+ SetResult(Value::CreateIntegerValue(result));
+ return;
+ }
+
+ result = static_cast<UDPSocket*>(socket)->JoinGroup(params_->address);
+ if (result != 0) {
+ error_ = net::ErrorToString(result);
+ }
+ SetResult(Value::CreateIntegerValue(result));
+}
+
+
+SocketLeaveGroupFunction::SocketLeaveGroupFunction()
+ : params_(NULL) {}
+
+SocketLeaveGroupFunction::~SocketLeaveGroupFunction() {}
+
+bool SocketLeaveGroupFunction::Prepare() {
+ params_ = api::socket::LeaveGroup::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketLeaveGroupFunction::Work() {
+ int result = -1;
+ Socket* socket = GetSocket(params_->socket_id);
+
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(Value::CreateIntegerValue(result));
+ return;
+ }
+
+ if (socket->GetSocketType() != Socket::TYPE_UDP) {
+ error_ = kMulticastSocketTypeError;
+ SetResult(Value::CreateIntegerValue(result));
+ return;
+ }
+
+ SocketPermission::CheckParam param(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
+ kWildcardAddress,
+ kWildcardPort);
+ if (!GetExtension()->CheckAPIPermissionWithParam(APIPermission::kSocket,
+ &param)) {
+ error_ = kPermissionError;
+ SetResult(Value::CreateIntegerValue(result));
+ return;
+ }
+
+ result = static_cast<UDPSocket*>(socket)->LeaveGroup(params_->address);
+ if (result != 0)
+ error_ = net::ErrorToString(result);
+ SetResult(Value::CreateIntegerValue(result));
+}
+
+SocketSetMulticastTimeToLiveFunction::SocketSetMulticastTimeToLiveFunction()
+ : params_(NULL) {}
+
+SocketSetMulticastTimeToLiveFunction::~SocketSetMulticastTimeToLiveFunction() {}
+
+bool SocketSetMulticastTimeToLiveFunction::Prepare() {
+ params_ = api::socket::SetMulticastTimeToLive::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+void SocketSetMulticastTimeToLiveFunction::Work() {
+ int result = -1;
+ Socket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(Value::CreateIntegerValue(result));
+ return;
+ }
+
+ if (socket->GetSocketType() != Socket::TYPE_UDP) {
+ error_ = kMulticastSocketTypeError;
+ SetResult(Value::CreateIntegerValue(result));
+ return;
+ }
+
+ result = static_cast<UDPSocket*>(socket)->SetMulticastTimeToLive(
+ params_->ttl);
+ if (result != 0)
+ error_ = net::ErrorToString(result);
+ SetResult(Value::CreateIntegerValue(result));
+}
+
+SocketSetMulticastLoopbackModeFunction::SocketSetMulticastLoopbackModeFunction()
+ : params_(NULL) {}
+
+SocketSetMulticastLoopbackModeFunction::
+ ~SocketSetMulticastLoopbackModeFunction() {}
+
+bool SocketSetMulticastLoopbackModeFunction::Prepare() {
+ params_ = api::socket::SetMulticastLoopbackMode::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketSetMulticastLoopbackModeFunction::Work() {
+ int result = -1;
+ Socket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(Value::CreateIntegerValue(result));
+ return;
+ }
+
+ if (socket->GetSocketType() != Socket::TYPE_UDP) {
+ error_ = kMulticastSocketTypeError;
+ SetResult(Value::CreateIntegerValue(result));
+ return;
+ }
+
+ result = static_cast<UDPSocket*>(socket)->
+ SetMulticastLoopbackMode(params_->enabled);
+ if (result != 0)
+ error_ = net::ErrorToString(result);
+ SetResult(Value::CreateIntegerValue(result));
+}
+
+SocketGetJoinedGroupsFunction::SocketGetJoinedGroupsFunction()
+ : params_(NULL) {}
+
+SocketGetJoinedGroupsFunction::~SocketGetJoinedGroupsFunction() {}
+
+bool SocketGetJoinedGroupsFunction::Prepare() {
+ params_ = api::socket::GetJoinedGroups::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params_.get());
+ return true;
+}
+
+void SocketGetJoinedGroupsFunction::Work() {
+ int result = -1;
+ Socket* socket = GetSocket(params_->socket_id);
+ if (!socket) {
+ error_ = kSocketNotFoundError;
+ SetResult(Value::CreateIntegerValue(result));
+ return;
+ }
+
+ if (socket->GetSocketType() != Socket::TYPE_UDP) {
+ error_ = kMulticastSocketTypeError;
+ SetResult(Value::CreateIntegerValue(result));
+ return;
+ }
+
+ SocketPermission::CheckParam param(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP,
+ kWildcardAddress,
+ kWildcardPort);
+ if (!GetExtension()->CheckAPIPermissionWithParam(APIPermission::kSocket,
+ &param)) {
+ error_ = kPermissionError;
+ SetResult(Value::CreateIntegerValue(result));
+ return;
+ }
+
+ base::ListValue* values = new base::ListValue();
+ values->AppendStrings((std::vector<std::string>&)
+ static_cast<UDPSocket*>(socket)->GetJoinedGroups());
+ SetResult(values);
+}
+
} // namespace extensions
diff --git a/chrome/browser/extensions/api/socket/socket_api.h b/chrome/browser/extensions/api/socket/socket_api.h
index 34013a4..99a7ef2 100644
--- a/chrome/browser/extensions/api/socket/socket_api.h
+++ b/chrome/browser/extensions/api/socket/socket_api.h
@@ -349,6 +349,93 @@ class SocketGetNetworkListFunction : public AsyncExtensionFunction {
void SendResponseOnUIThread(const net::NetworkInterfaceList& interface_list);
};
+class SocketJoinGroupFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.joinGroup", SOCKET_MULTICAST_JOIN_GROUP)
+
+ SocketJoinGroupFunction();
+
+ protected:
+ virtual ~SocketJoinGroupFunction();
+
+ // AsyncApiFunction
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<api::socket::JoinGroup::Params> params_;
+};
+
+class SocketLeaveGroupFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.leaveGroup", SOCKET_MULTICAST_LEAVE_GROUP)
+
+ SocketLeaveGroupFunction();
+
+ protected:
+ virtual ~SocketLeaveGroupFunction();
+
+ // AsyncApiFunction
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<api::socket::LeaveGroup::Params> params_;
+};
+
+class SocketSetMulticastTimeToLiveFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.setMulticastTimeToLive",
+ SOCKET_MULTICAST_SET_TIME_TO_LIVE)
+
+ SocketSetMulticastTimeToLiveFunction();
+
+ protected:
+ virtual ~SocketSetMulticastTimeToLiveFunction();
+
+ // AsyncApiFunction
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<api::socket::SetMulticastTimeToLive::Params> params_;
+};
+
+class SocketSetMulticastLoopbackModeFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.setMulticastLoopbackMode",
+ SOCKET_MULTICAST_SET_LOOPBACK_MODE)
+
+ SocketSetMulticastLoopbackModeFunction();
+
+ protected:
+ virtual ~SocketSetMulticastLoopbackModeFunction();
+
+ // AsyncApiFunction
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<api::socket::SetMulticastLoopbackMode::Params> params_;
+};
+
+class SocketGetJoinedGroupsFunction : public SocketAsyncApiFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("socket.getJoinedGroups",
+ SOCKET_MULTICAST_GET_JOINED_GROUPS)
+
+ SocketGetJoinedGroupsFunction();
+
+ protected:
+ virtual ~SocketGetJoinedGroupsFunction();
+
+ // AsyncApiFunction
+ virtual bool Prepare() OVERRIDE;
+ virtual void Work() OVERRIDE;
+
+ private:
+ scoped_ptr<api::socket::GetJoinedGroups::Params> params_;
+};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_API_SOCKET_SOCKET_API_H_
diff --git a/chrome/browser/extensions/api/socket/socket_apitest.cc b/chrome/browser/extensions/api/socket/socket_apitest.cc
index 7921ce9..b9b4451 100644
--- a/chrome/browser/extensions/api/socket/socket_apitest.cc
+++ b/chrome/browser/extensions/api/socket/socket_apitest.cc
@@ -199,3 +199,15 @@ IN_PROC_BROWSER_TEST_F(SocketApiTest, SocketTCPServerUnbindOnUnload) {
<< message_;
EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}
+
+IN_PROC_BROWSER_TEST_F(SocketApiTest, SocketMulticast) {
+ ResultCatcher catcher;
+ catcher.RestrictToProfile(browser()->profile());
+ ExtensionTestMessageListener listener("info_please", true);
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("socket/api")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ listener.Reply(
+ base::StringPrintf("multicast:%s:%d", kHostname.c_str(), kPort));
+
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
diff --git a/chrome/browser/extensions/api/socket/udp_socket.cc b/chrome/browser/extensions/api/socket/udp_socket.cc
index b9a804c..3f6080d 100644
--- a/chrome/browser/extensions/api/socket/udp_socket.cc
+++ b/chrome/browser/extensions/api/socket/udp_socket.cc
@@ -4,6 +4,8 @@
#include "chrome/browser/extensions/api/socket/udp_socket.h"
+#include <algorithm>
+
#include "chrome/browser/extensions/api/api_resource.h"
#include "net/base/ip_endpoint.h"
#include "net/base/net_errors.h"
@@ -222,4 +224,54 @@ void UDPSocket::OnSendToComplete(int result) {
send_to_callback_.Reset();
}
+int UDPSocket::JoinGroup(const std::string& address) {
+ net::IPAddressNumber ip;
+ if (!net::ParseIPLiteralToNumber(address, &ip))
+ return net::ERR_ADDRESS_INVALID;
+
+ std::string normalized_address = net::IPAddressToString(ip);
+ std::vector<std::string>::iterator find_result =
+ std::find(multicast_groups_.begin(),
+ multicast_groups_.end(),
+ normalized_address);
+ if (find_result != multicast_groups_.end())
+ return net::ERR_ADDRESS_INVALID;
+
+ int rv = socket_.JoinGroup(ip);
+ if (rv == 0)
+ multicast_groups_.push_back(normalized_address);
+ return rv;
+}
+
+int UDPSocket::LeaveGroup(const std::string& address) {
+ net::IPAddressNumber ip;
+ if (!net::ParseIPLiteralToNumber(address, &ip))
+ return net::ERR_ADDRESS_INVALID;
+
+ std::string normalized_address = net::IPAddressToString(ip);
+ std::vector<std::string>::iterator find_result =
+ std::find(multicast_groups_.begin(),
+ multicast_groups_.end(),
+ normalized_address);
+ if (find_result == multicast_groups_.end())
+ return net::ERR_ADDRESS_INVALID;
+
+ int rv = socket_.LeaveGroup(ip);
+ if (rv == 0)
+ multicast_groups_.erase(find_result);
+ return rv;
+}
+
+int UDPSocket::SetMulticastTimeToLive(int ttl) {
+ return socket_.SetMulticastTimeToLive(ttl);
+}
+
+int UDPSocket::SetMulticastLoopbackMode(bool loopback) {
+ return socket_.SetMulticastLoopbackMode(loopback);
+}
+
+const std::vector<std::string>& UDPSocket::GetJoinedGroups() const {
+ return multicast_groups_;
+}
+
} // namespace extensions
diff --git a/chrome/browser/extensions/api/socket/udp_socket.h b/chrome/browser/extensions/api/socket/udp_socket.h
index 8e2d2e6..5e02850 100644
--- a/chrome/browser/extensions/api/socket/udp_socket.h
+++ b/chrome/browser/extensions/api/socket/udp_socket.h
@@ -6,6 +6,7 @@
#define CHROME_BROWSER_EXTENSIONS_API_SOCKET_UDP_SOCKET_H_
#include <string>
+#include <vector>
#include "chrome/browser/extensions/api/socket/socket.h"
#include "net/udp/udp_socket.h"
@@ -35,6 +36,14 @@ class UDPSocket : public Socket {
virtual bool GetLocalAddress(net::IPEndPoint* address) OVERRIDE;
virtual Socket::SocketType GetSocketType() const OVERRIDE;
+ int JoinGroup(const std::string& address);
+ int LeaveGroup(const std::string& address);
+
+ int SetMulticastTimeToLive(int ttl);
+ int SetMulticastLoopbackMode(bool loopback);
+
+ const std::vector<std::string>& GetJoinedGroups() const;
+
protected:
virtual int WriteImpl(net::IOBuffer* io_buffer,
int io_buffer_size,
@@ -58,6 +67,8 @@ class UDPSocket : public Socket {
RecvFromCompletionCallback recv_from_callback_;
CompletionCallback send_to_callback_;
+
+ std::vector<std::string> multicast_groups_;
};
} // namespace extensions
diff --git a/chrome/browser/extensions/api/socket/udp_socket_unittest.cc b/chrome/browser/extensions/api/socket/udp_socket_unittest.cc
index c625efe..0730d64 100644
--- a/chrome/browser/extensions/api/socket/udp_socket_unittest.cc
+++ b/chrome/browser/extensions/api/socket/udp_socket_unittest.cc
@@ -8,6 +8,7 @@
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
+#include "base/test/test_timeouts.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "net/base/io_buffer.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -20,7 +21,7 @@ class UDPSocketUnitTest : public BrowserWithTestWindowTest {
};
static void OnConnected(int result) {
- DCHECK(result == 0);
+ EXPECT_EQ(0, result);
}
static void OnCompleted(int bytes_read,
@@ -30,8 +31,15 @@ static void OnCompleted(int bytes_read,
// Do nothing; don't care.
}
+static const char test_message[] = "$$TESTMESSAGETESTMESSAGETESTMESSAGETEST$$";
+static const int test_message_length = ARRAYSIZE_UNSAFE(test_message);
+
+static void OnSendCompleted(int result) {
+ EXPECT_EQ(test_message_length, result);
+}
+
TEST(UDPSocketUnitTest, TestUDPSocketRecvFrom) {
- MessageLoopForIO io_loop; // for RecvFrom to do its threaded work.
+ MessageLoopForIO io_loop; // For RecvFrom to do its threaded work.
UDPSocket socket("abcdefghijklmnopqrst");
// Confirm that we can call two RecvFroms in quick succession without
@@ -41,4 +49,90 @@ TEST(UDPSocketUnitTest, TestUDPSocketRecvFrom) {
socket.RecvFrom(4096, base::Bind(&OnCompleted));
}
+TEST(UDPSocketUnitTest, TestUDPMulticastJoinGroup) {
+ const char* kGroup = "237.132.100.17";
+ UDPSocket src("abcdefghijklmnopqrst");
+ UDPSocket dest("abcdefghijklmnopqrst");
+
+ EXPECT_EQ(0, dest.Bind("0.0.0.0", 13333));
+ EXPECT_EQ(0, dest.JoinGroup(kGroup));
+ std::vector<std::string> groups = dest.GetJoinedGroups();
+ EXPECT_EQ(static_cast<size_t>(1), groups.size());
+ EXPECT_EQ(kGroup, *groups.begin());
+ EXPECT_NE(0, dest.LeaveGroup("237.132.100.13"));
+ EXPECT_EQ(0, dest.LeaveGroup(kGroup));
+ groups = dest.GetJoinedGroups();
+ EXPECT_EQ(static_cast<size_t>(0), groups.size());
+}
+
+TEST(UDPSocketUnitTest, TestUDPMulticastTimeToLive) {
+ const char* kGroup = "237.132.100.17";
+ UDPSocket socket("abcdefghijklmnopqrst");
+ EXPECT_NE(0, socket.SetMulticastTimeToLive(-1)); // Negative TTL shall fail.
+ EXPECT_EQ(0, socket.SetMulticastTimeToLive(3));
+ socket.Connect(kGroup, 13333, base::Bind(&OnConnected));
+}
+
+TEST(UDPSocketUnitTest, TestUDPMulticastLoopbackMode) {
+ const char* kGroup = "237.132.100.17";
+ UDPSocket socket("abcdefghijklmnopqrst");
+ EXPECT_EQ(0, socket.SetMulticastLoopbackMode(false));
+ socket.Connect(kGroup, 13333, base::Bind(&OnConnected));
+}
+
+static void QuitMessageLoop() {
+ MessageLoopForIO::current()->QuitNow();
+}
+
+// Send a test multicast packet every second.
+// Once the target socket received the packet, the message loop will exit.
+static void SendMulticastPacket(UDPSocket* src, int result) {
+ if (result == 0) {
+ scoped_refptr<net::IOBuffer> data = new net::WrappedIOBuffer(test_message);
+ src->Write(data, test_message_length, base::Bind(&OnSendCompleted));
+ MessageLoopForIO::current()->PostDelayedTask(FROM_HERE,
+ base::Bind(&SendMulticastPacket, src, result),
+ base::TimeDelta::FromSeconds(1));
+ } else {
+ QuitMessageLoop();
+ FAIL() << "Failed to connect to multicast address. Error code: " << result;
+ }
+}
+
+static void OnMulticastReadCompleted(bool *packet_received,
+ int count,
+ scoped_refptr<net::IOBuffer> io_buffer) {
+ EXPECT_EQ(test_message_length, count);
+ EXPECT_EQ(0, strncmp(io_buffer->data(), test_message, test_message_length));
+ *packet_received = true;
+ QuitMessageLoop();
+}
+
+TEST(UDPSocketUnitTest, TestUDPMulticastRecv) {
+ const int kPort = 9999;
+ const char* const kGroup = "237.132.100.17";
+ bool packet_received = false;
+ MessageLoopForIO io_loop; // For Read to do its threaded work.
+ UDPSocket dest("abcdefghijklmnopqrst");
+ UDPSocket src("abcdefghijklmnopqrst");
+
+ // Receiver
+ EXPECT_EQ(0, dest.Bind("0.0.0.0", kPort));
+ EXPECT_EQ(0, dest.JoinGroup(kGroup));
+ dest.Read(1024, base::Bind(&OnMulticastReadCompleted, &packet_received));
+
+ // Sender
+ EXPECT_EQ(0, src.SetMulticastTimeToLive(0));
+ src.Connect(kGroup, kPort, base::Bind(&SendMulticastPacket, &src));
+
+ // If not received within the test action timeout, quit the message loop.
+ io_loop.PostDelayedTask(FROM_HERE,
+ base::Bind(&QuitMessageLoop),
+ TestTimeouts::action_timeout());
+
+ io_loop.Run();
+
+ EXPECT_TRUE(packet_received) << "Failed to receive from multicast address";
+}
+
} // namespace extensions
diff --git a/chrome/browser/extensions/extension_function_histogram_value.h b/chrome/browser/extensions/extension_function_histogram_value.h
index e730384..13406cd 100644
--- a/chrome/browser/extensions/extension_function_histogram_value.h
+++ b/chrome/browser/extensions/extension_function_histogram_value.h
@@ -520,6 +520,11 @@ enum HistogramValue {
AUDIO_SETACTIVEDEVICES,
AUDIO_SETPROPERTIES,
USB_RESETDEVICE,
+ SOCKET_MULTICAST_JOIN_GROUP,
+ SOCKET_MULTICAST_LEAVE_GROUP,
+ SOCKET_MULTICAST_SET_TIME_TO_LIVE,
+ SOCKET_MULTICAST_SET_LOOPBACK_MODE,
+ SOCKET_MULTICAST_GET_JOINED_GROUPS,
ENUM_BOUNDARY // Last entry: Add new entries above.
};
diff --git a/chrome/common/extensions/api/socket.idl b/chrome/common/extensions/api/socket.idl
index 3cb999c..4bc6068 100644
--- a/chrome/common/extensions/api/socket.idl
+++ b/chrome/common/extensions/api/socket.idl
@@ -114,6 +114,16 @@ namespace socket {
callback GetNetworkCallback = void (NetworkInterface[] result);
+ callback JoinGroupCallback = void (long result);
+
+ callback LeaveGroupCallback = void (long result);
+
+ callback SetMulticastTimeToLiveCallback = void (long result);
+
+ callback SetMulticastLoopbackModeCallback = void (long result);
+
+ callback GetJoinedGroupsCallback = void (DOMString[] groups);
+
interface Functions {
// Creates a socket of the specified type that will connect to the specified
// remote machine.
@@ -205,6 +215,7 @@ namespace socket {
// |address| : The address of the local machine.
// |port| : The port of the local machine.
// |backlog| : Length of the socket's listen queue.
+ // |callback| : Called when listen operation completes.
static void listen(long socketId,
DOMString address,
long port,
@@ -250,6 +261,72 @@ namespace socket {
// Retrieves information about local adapters on this system.
// |callback| : Called when local adapter information is available.
static void getNetworkList(GetNetworkCallback callback);
+
+ // Join the multicast group and start to receive packets from that group.
+ // The socket must be of UDP type and must be bound to a local port
+ // before calling this method.
+ // |socketId| : The socketId.
+ // |address| : The group address to join. Domain names are not supported.
+ // |callback| : Called when the join group operation is done with an
+ // integer parameter indicating the platform-independent error code.
+ static void joinGroup(long socketId,
+ DOMString address,
+ JoinGroupCallback callback);
+
+ // Leave the multicast group previously joined using <code>joinGroup</code>.
+ // It's not necessary to leave the multicast group before destroying the
+ // socket or exiting. This is automatically called by the OS.
+ //
+ // Leaving the group will prevent the router from sending multicast
+ // datagrams to the local host, presuming no other process on the host is
+ // still joined to the group.
+ //
+ // |socketId| : The socketId.
+ // |address| : The group address to leave. Domain names are not supported.
+ // |callback| : Called when the leave group operation is done with an
+ // integer parameter indicating the platform-independent error code.
+ static void leaveGroup(long socketId, DOMString address,
+ LeaveGroupCallback callback);
+
+ // Set the time-to-live of multicast packets sent to the multicast group.
+ //
+ // Calling this method does not require multicast permissions.
+ //
+ // |socketId| : The socketId.
+ // |ttl| : The time-to-live value.
+ // |callback| : Called when the configuration operation is done.
+ static void setMulticastTimeToLive(
+ long socketId,
+ long ttl,
+ SetMulticastTimeToLiveCallback callback);
+
+ // Set whether multicast packets sent from the host to the multicast
+ // group will be looped back to the host.
+ //
+ // Note: the behavior of <code>setMulticastLoopbackMode</code> is slightly
+ // different between Windows and Unix-like systems. The inconsistency
+ // happens only when there is more than one application on the same host
+ // joined to the same multicast group while having different settings on
+ // multicast loopback mode. On Windows, the applications with loopback off
+ // will not RECEIVE the loopback packets; while on Unix-like systems, the
+ // applications with loopback off will not SEND the loopback packets to
+ // other applications on the same host. See MSDN: http://goo.gl/6vqbj
+ //
+ // Calling this method does not require multicast permissions.
+ //
+ // |socketId| : The socketId.
+ // |enabled| : Indicate whether to enable loopback mode.
+ // |callback| : Called when the configuration operation is done.
+ static void setMulticastLoopbackMode(
+ long socketId,
+ boolean enabled,
+ SetMulticastLoopbackModeCallback callback);
+
+ // Get the multicast group addresses the socket is currently joined to.
+ // |socketId| : The socketId.
+ // |callback| : Called with an array of strings of the result.
+ static void getJoinedGroups(long socketId,
+ GetJoinedGroupsCallback callback);
};
};
diff --git a/chrome/common/extensions/permissions/socket_permission_data.cc b/chrome/common/extensions/permissions/socket_permission_data.cc
index e44844c..316060a 100644
--- a/chrome/common/extensions/permissions/socket_permission_data.cc
+++ b/chrome/common/extensions/permissions/socket_permission_data.cc
@@ -30,6 +30,7 @@ const char kTCPConnect[] = "tcp-connect";
const char kTCPListen[] = "tcp-listen";
const char kUDPBind[] = "udp-bind";
const char kUDPSendTo[] = "udp-send-to";
+const char kUDPMulticastMembership[] = "udp-multicast-membership";
const int kWildcardPortNumber = 0;
const int kInvalidPort = -1;
@@ -42,6 +43,8 @@ SocketPermissionRequest::OperationType StringToType(const std::string& s) {
return SocketPermissionRequest::UDP_BIND;
if (s == kUDPSendTo)
return SocketPermissionRequest::UDP_SEND_TO;
+ if (s == kUDPMulticastMembership)
+ return SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP;
return SocketPermissionRequest::NONE;
}
@@ -55,6 +58,8 @@ const char* TypeToString(SocketPermissionRequest::OperationType type) {
return kUDPBind;
case SocketPermissionRequest::UDP_SEND_TO:
return kUDPSendTo;
+ case SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP:
+ return kUDPMulticastMembership;
default:
return kInvalid;
}
@@ -207,6 +212,10 @@ bool SocketPermissionData::Parse(const std::string& permission) {
if (tokens.size() == 1)
return true;
+ // Multicast membership permission string does not include an address.
+ if (pattern_.type == SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP)
+ return false;
+
pattern_.host = tokens[1];
if (!pattern_.host.empty()) {
if (StartsOrEndsWithWhitespace(pattern_.host))
@@ -250,6 +259,9 @@ const std::string& SocketPermissionData::GetAsString() const {
spec_.reserve(64);
spec_.append(TypeToString(pattern_.type));
+ if (pattern_.type == SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP)
+ return spec_;
+
if (match_subdomains_) {
spec_.append(1, kColon).append(kWildcard);
if (!pattern_.host.empty())
diff --git a/chrome/common/extensions/permissions/socket_permission_data.h b/chrome/common/extensions/permissions/socket_permission_data.h
index b97e213..118c613 100644
--- a/chrome/common/extensions/permissions/socket_permission_data.h
+++ b/chrome/common/extensions/permissions/socket_permission_data.h
@@ -17,7 +17,8 @@ namespace extensions {
// := <op> |
// <op> ':' <host> |
// <op> ':' ':' <port> |
-// <op> ':' <host> ':' <port>
+// <op> ':' <host> ':' <port> |
+// 'udp-multicast-membership'
// <op> := 'tcp-connect' |
// 'tcp-listen' |
// 'udp-bind' |
@@ -27,6 +28,7 @@ namespace extensions {
// <anychar except '/' and '*'>+
// <port> := '*' |
// <port number between 0 and 65535>)
+// The multicast membership permission implies a permission to any address.
class SocketPermissionData {
public:
enum HostType {
diff --git a/chrome/common/extensions/permissions/socket_permission_unittest.cc b/chrome/common/extensions/permissions/socket_permission_unittest.cc
index 05e7d3d..b04c3df 100644
--- a/chrome/common/extensions/permissions/socket_permission_unittest.cc
+++ b/chrome/common/extensions/permissions/socket_permission_unittest.cc
@@ -198,6 +198,25 @@ TEST_F(SocketPermissionTest, Match) {
param.reset(new SocketPermission::CheckParam(
SocketPermissionRequest::TCP_CONNECT, "192.168.0.1", 8800));
EXPECT_FALSE(data.Check(param.get()));
+
+ ASSERT_FALSE(data.ParseForTest("udp-multicast-membership:*"));
+ ASSERT_FALSE(data.ParseForTest("udp-multicast-membership:*:*"));
+ ASSERT_TRUE(data.ParseForTest("udp-multicast-membership"));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_BIND, "127.0.0.1", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_BIND, "127.0.0.1", 8888));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::TCP_CONNECT, "www.example.com", 80));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_SEND_TO, "www.google.com", 8800));
+ EXPECT_FALSE(data.Check(param.get()));
+ param.reset(new SocketPermission::CheckParam(
+ SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP, "127.0.0.1", 35));
+ EXPECT_TRUE(data.Check(param.get()));
}
TEST_F(SocketPermissionTest, IPC) {
diff --git a/chrome/test/data/extensions/api_test/socket/api/background.js b/chrome/test/data/extensions/api_test/socket/api/background.js
index 7f78b56..5b3b425 100644
--- a/chrome/test/data/extensions/api_test/socket/api/background.js
+++ b/chrome/test/data/extensions/api_test/socket/api/background.js
@@ -42,26 +42,23 @@ function arrayBuffer2String(buf, callback) {
}
var testSocketCreation = function() {
- function onGetInfo(info) {
- chrome.test.assertEq(info.socketType, protocol);
- chrome.test.assertFalse(info.connected);
+ function onCreate(socketInfo) {
+ function onGetInfo(info) {
+ chrome.test.assertEq(info.socketType, protocol);
+ chrome.test.assertFalse(info.connected);
- if (info.peerAddress || info.peerPort) {
- chrome.test.fail('Unconnected socket should not have peer');
- }
- if (info.localAddress || info.localPort) {
- chrome.test.fail('Unconnected socket should not have local binding');
- }
+ if (info.peerAddress || info.peerPort) {
+ chrome.test.fail('Unconnected socket should not have peer');
+ }
+ if (info.localAddress || info.localPort) {
+ chrome.test.fail('Unconnected socket should not have local binding');
+ }
- // TODO(miket): this doesn't work yet. It's possible this will become
- // automatic, but either way we can't forget to clean up.
- //
- //socket.destroy(socketInfo.socketId);
+ socket.destroy(socketInfo.socketId);
- chrome.test.succeed();
- }
+ chrome.test.succeed();
+ }
- function onCreate(socketInfo) {
chrome.test.assertTrue(socketInfo.socketId > 0);
// Obtaining socket information before a connect() call should be safe, but
@@ -236,13 +233,16 @@ var testSocketListening = function() {
var onMessageReply = function(message) {
var parts = message.split(":");
- test_type = parts[0];
+ var test_type = parts[0];
address = parts[1];
port = parseInt(parts[2]);
console.log("Running tests, protocol " + protocol + ", echo server " +
address + ":" + port);
if (test_type == 'tcp_server') {
chrome.test.runTests([ testSocketListening ]);
+ } else if (test_type == 'multicast') {
+ console.log("Running multicast tests");
+ chrome.test.runTests([ testMulticast ]);
} else {
protocol = test_type;
chrome.test.runTests([ testSocketCreation, testSending ]);
diff --git a/chrome/test/data/extensions/api_test/socket/api/manifest.json b/chrome/test/data/extensions/api_test/socket/api/manifest.json
index 213a631..3857064 100644
--- a/chrome/test/data/extensions/api_test/socket/api/manifest.json
+++ b/chrome/test/data/extensions/api_test/socket/api/manifest.json
@@ -4,7 +4,7 @@
"description": "end-to-end browser test for chrome.socket API",
"app": {
"background": {
- "scripts": ["background.js"]
+ "scripts": ["multicast.js", "background.js"]
}
},
"permissions": [
@@ -12,7 +12,8 @@
"tcp-connect",
"tcp-listen",
"udp-send-to",
- "udp-bind"
+ "udp-bind",
+ "udp-multicast-membership"
]}
]
}
diff --git a/chrome/test/data/extensions/api_test/socket/api/multicast.js b/chrome/test/data/extensions/api_test/socket/api/multicast.js
new file mode 100644
index 0000000..2447a82
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/socket/api/multicast.js
@@ -0,0 +1,198 @@
+// Copyright (c) 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Tests for multicast UDP socket.
+function testMulticast() {
+ function randomHexString(count) {
+ var result = '';
+ for (var i = 0; i < count; i++) {
+ result += (Math.random() * 16 >> 0).toString(16);
+ }
+ return result;
+ }
+
+ var kMulticastAddress = "237.132.100.133";
+ var kTestMessageLength = 128;
+ var kTestMessage = randomHexString(128);
+ var kPort = 11103;
+
+ function arrayBufferToString(arrayBuffer) {
+ // UTF-16LE
+ return String.fromCharCode.apply(String, new Uint16Array(arrayBuffer));
+ }
+
+ function stringToArrayBuffer(string) {
+ // UTF-16LE
+ var buf = new ArrayBuffer(string.length * 2);
+ var bufView = new Uint16Array(buf);
+ for (var i = 0, strLen = string.length; i < strLen; i++) {
+ bufView[i] = string.charCodeAt(i);
+ }
+ return buf;
+ }
+
+ function waitForMessage(socketId, callback) {
+ var cancelled = false;
+ var relayCanceller = null;
+ socket.recvFrom(socketId, 1024, function (result) {
+ if (cancelled)
+ return;
+
+ if (result.resultCode == kTestMessageLength * 2 &&
+ kTestMessage === arrayBufferToString(result.data)) {
+ callback(cancelled);
+ } else {
+ // Restart waiting.
+ relayCanceller = waitForMessage(socketId, callback);
+ }
+ });
+ return function canceller() {
+ if (relayCanceller) {
+ relayCanceller();
+ } else {
+ cancelled = true;
+ callback(true);
+ }
+ };
+ }
+
+ function testMulticastSettings() {
+ socket.create('udp', {}, function (socketInfo) {
+ var socket_id;
+ if (socketInfo) {
+ socket_id = socketInfo.socketId;
+ socket.setMulticastTimeToLive(socket_id, 0, function (result) {
+ chrome.test.assertEq(0, result,
+ "Error setting multicast time to live.");
+ socket.setMulticastTimeToLive(socket_id, -3, function (result) {
+ chrome.test.assertEq(-4, result,
+ "Error setting multicast time to live.");
+ socket.setMulticastLoopbackMode(socket_id, false,
+ function (result) {
+ chrome.test.assertEq(0, result,
+ "Error setting multicast loop back mode.");
+ socket.setMulticastLoopbackMode(socket_id, true,
+ function (result) {
+ chrome.test.assertEq(0, result,
+ "Error setting multicast loop back mode.");
+ socket.destroy(socket_id);
+ testMulticastRecv();
+ });
+ });
+ });
+ });
+ } else {
+ chrome.test.fail("Cannot create server udp socket");
+ }
+ });
+ }
+
+ function testSendMessage(message, address) {
+ // Send the UDP message to the address with multicast ttl = 0.
+ address = address || kMulticastAddress;
+ socket.create('udp', {}, function (socketInfo) {
+ var clientSocketId;
+ if (socketInfo) {
+ clientSocketId = socketInfo.socketId;
+ chrome.test.assertTrue(clientSocketId > 0,
+ "Cannot create client udp socket.");
+ socket.setMulticastTimeToLive(clientSocketId, 0, function (result) {
+ chrome.test.assertEq(0, result,
+ "Cannot create client udp socket.");
+ socket.connect(clientSocketId, address, kPort, function (result) {
+ chrome.test.assertEq(0, result,
+ "Cannot connect to localhost.");
+ socket.write(clientSocketId, stringToArrayBuffer(kTestMessage),
+ function (result) {
+ chrome.test.assertTrue(result.bytesWritten >= 0,
+ "Send to failed. " + JSON.stringify(result));
+ socket.destroy(clientSocketId);
+ });
+ });
+ });
+ } else {
+ chrome.test.fail("Cannot create client udp socket");
+ }
+ });
+ }
+
+ function recvBeforeAddMembership(serverSocketId) {
+ var recvTimeout;
+ var canceller = waitForMessage(serverSocketId, function (cancelled) {
+ clearTimeout(recvTimeout);
+ if (cancelled) {
+ recvWithMembership(serverSocketId);
+ } else {
+ chrome.test.fail("Received message before joining the group");
+ }
+ });
+ testSendMessage(kTestMessage); // Meant to be lost.
+ recvTimeout = setTimeout(function () {
+ canceller();
+ testSendMessage(kTestMessage, "127.0.0.1", kPort);
+ }, 2000);
+ }
+
+ function recvWithMembership(serverSocketId) {
+ // Join group.
+ socket.joinGroup(serverSocketId, kMulticastAddress, function (result) {
+ chrome.test.assertEq(0, result, "Join group failed.");
+ var recvTimeout;
+ var canceller = waitForMessage(serverSocketId, function (cancelled) {
+ clearTimeout(recvTimeout);
+ if (!cancelled) {
+ recvWithoutMembership(serverSocketId);
+ } else {
+ chrome.test.fail("Faild to receive message after joining the group");
+ }
+ });
+ testSendMessage(kTestMessage);
+ recvTimeout = setTimeout(function () {
+ canceller();
+ socket.destroy(serverSocketId);
+ chrome.test.fail("Cannot receive from multicast group.");
+ }, 2000);
+ });
+ }
+
+ function recvWithoutMembership(serverSocketId) {
+ socket.leaveGroup(serverSocketId, kMulticastAddress, function (result) {
+ chrome.test.assertEq(0, result, "leave group failed.");
+ var recvTimeout;
+ var canceller = waitForMessage(serverSocketId, function (cancelled) {
+ clearTimeout(recvTimeout);
+ if (cancelled) {
+ socket.destroy(serverSocketId);
+ chrome.test.succeed();
+ } else {
+ chrome.test.fail("Received message after leaving the group");
+ socket.destroy(serverSocketId);
+ }
+ });
+ testSendMessage(request);
+ recvTimeout = setTimeout(function () {
+ canceller();
+ socket.destroy(serverSocketId);
+ chrome.test.succeed();
+ }, 2000);
+ });
+ }
+
+ function testMulticastRecv() {
+ socket.create('udp', {}, function (socketInfo) {
+ var serverSocketId;
+ if (socketInfo) {
+ serverSocketId = socketInfo.socketId;
+ socket.bind(serverSocketId, "0.0.0.0", kPort, function (result) {
+ chrome.test.assertEq(0, result, "Bind failed.");
+ recvBeforeAddMembership(serverSocketId);
+ });
+ } else {
+ chrome.test.fail("Cannot create server udp socket");
+ }
+ });
+ }
+
+ testMulticastSettings();
+} \ No newline at end of file
diff --git a/content/public/common/socket_permission_request.h b/content/public/common/socket_permission_request.h
index 2743eeb..d2d0ea2 100644
--- a/content/public/common/socket_permission_request.h
+++ b/content/public/common/socket_permission_request.h
@@ -18,6 +18,7 @@ struct SocketPermissionRequest {
TCP_LISTEN,
UDP_BIND,
UDP_SEND_TO,
+ UDP_MULTICAST_MEMBERSHIP
};
SocketPermissionRequest(OperationType type,
diff --git a/net/udp/udp_socket_libevent.cc b/net/udp/udp_socket_libevent.cc
index 063da08..f713786 100644
--- a/net/udp/udp_socket_libevent.cc
+++ b/net/udp/udp_socket_libevent.cc
@@ -41,7 +41,9 @@ UDPSocketLibevent::UDPSocketLibevent(
net::NetLog* net_log,
const net::NetLog::Source& source)
: socket_(kInvalidSocket),
- socket_options_(0),
+ addr_family_(0),
+ socket_options_(SOCKET_OPTION_MULTICAST_LOOP),
+ multicast_time_to_live_(1),
bind_type_(bind_type),
rand_int_cb_(rand_int_cb),
read_watcher_(this),
@@ -363,7 +365,8 @@ void UDPSocketLibevent::LogRead(int result,
}
int UDPSocketLibevent::CreateSocket(const IPEndPoint& address) {
- socket_ = socket(address.GetSockAddrFamily(), SOCK_DGRAM, 0);
+ addr_family_ = address.GetSockAddrFamily();
+ socket_ = socket(addr_family_, SOCK_DGRAM, 0);
if (socket_ == kInvalidSocket)
return MapSystemError(errno);
if (SetNonBlocking(socket_)) {
@@ -476,11 +479,40 @@ int UDPSocketLibevent::SetSocketOptions() {
// port.
rv = setsockopt(socket_, SOL_SOCKET, SO_REUSEPORT, &true_value,
sizeof(true_value));
- if (rv < 0)
- return MapSystemError(errno);
-#endif // defined(OS_MACOSX)
+#else
rv = setsockopt(socket_, SOL_SOCKET, SO_BROADCAST, &true_value,
sizeof(true_value));
+#endif // defined(OS_MACOSX)
+ if (rv < 0)
+ return MapSystemError(errno);
+ }
+
+ if (!(socket_options_ & SOCKET_OPTION_MULTICAST_LOOP)) {
+ int rv;
+ if (addr_family_ == AF_INET) {
+ u_char loop = 0;
+ rv = setsockopt(socket_, IPPROTO_IP, IP_MULTICAST_LOOP,
+ &loop, sizeof(loop));
+ } else {
+ u_int loop = 0;
+ rv = setsockopt(socket_, IPPROTO_IPV6, IPV6_MULTICAST_LOOP,
+ &loop, sizeof(loop));
+ }
+ if (rv < 0)
+ return MapSystemError(errno);
+ }
+ if (multicast_time_to_live_ != IP_DEFAULT_MULTICAST_TTL) {
+ int rv;
+ if (addr_family_ == AF_INET) {
+ u_char ttl = multicast_time_to_live_;
+ rv = setsockopt(socket_, IPPROTO_IP, IP_MULTICAST_TTL,
+ &ttl, sizeof(ttl));
+ } else {
+ // Signed interger. -1 to use route default.
+ int ttl = multicast_time_to_live_;
+ rv = setsockopt(socket_, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
+ &ttl, sizeof(ttl));
+ }
if (rv < 0)
return MapSystemError(errno);
}
@@ -509,4 +541,99 @@ int UDPSocketLibevent::RandomBind(const IPEndPoint& address) {
return DoBind(IPEndPoint(ip, 0));
}
+int UDPSocketLibevent::JoinGroup(const IPAddressNumber& group_address) const {
+ DCHECK(CalledOnValidThread());
+ if (!is_connected())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ switch (group_address.size()) {
+ case kIPv4AddressSize: {
+ if (addr_family_ != AF_INET)
+ return ERR_ADDRESS_INVALID;
+ ip_mreq mreq;
+ mreq.imr_interface.s_addr = INADDR_ANY;
+ memcpy(&mreq.imr_multiaddr, &group_address[0], kIPv4AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ if (rv < 0)
+ return MapSystemError(errno);
+ return OK;
+ }
+ case kIPv6AddressSize: {
+ if (addr_family_ != AF_INET6)
+ return ERR_ADDRESS_INVALID;
+ ipv6_mreq mreq;
+ mreq.ipv6mr_interface = 0; // 0 indicates default multicast interface.
+ memcpy(&mreq.ipv6mr_multiaddr, &group_address[0], kIPv6AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq, sizeof(mreq));
+ if (rv < 0)
+ return MapSystemError(errno);
+ return OK;
+ }
+ default:
+ NOTREACHED() << "Invalid address family";
+ return ERR_ADDRESS_INVALID;
+ }
+}
+
+int UDPSocketLibevent::LeaveGroup(const IPAddressNumber& group_address) const {
+ DCHECK(CalledOnValidThread());
+
+ if (!is_connected())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ switch (group_address.size()) {
+ case kIPv4AddressSize: {
+ if (addr_family_ != AF_INET)
+ return ERR_ADDRESS_INVALID;
+ ip_mreq mreq;
+ mreq.imr_interface.s_addr = INADDR_ANY;
+ memcpy(&mreq.imr_multiaddr, &group_address[0], kIPv4AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IP, IP_DROP_MEMBERSHIP,
+ &mreq, sizeof(mreq));
+ if (rv < 0)
+ return MapSystemError(errno);
+ return OK;
+ }
+ case kIPv6AddressSize: {
+ if (addr_family_ != AF_INET6)
+ return ERR_ADDRESS_INVALID;
+ ipv6_mreq mreq;
+ mreq.ipv6mr_interface = 0; // 0 indicates default multicast interface.
+ memcpy(&mreq.ipv6mr_multiaddr, &group_address[0], kIPv6AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IPV6, IPV6_LEAVE_GROUP,
+ &mreq, sizeof(mreq));
+ if (rv < 0)
+ return MapSystemError(errno);
+ return OK;
+ }
+ default:
+ NOTREACHED() << "Invalid address family";
+ return ERR_ADDRESS_INVALID;
+ }
+}
+
+int UDPSocketLibevent::SetMulticastTimeToLive(int time_to_live) {
+ DCHECK(CalledOnValidThread());
+ if (is_connected())
+ return ERR_UNEXPECTED;
+
+ if (time_to_live < 0 || time_to_live > 255)
+ return ERR_INVALID_ARGUMENT;
+ multicast_time_to_live_ = time_to_live;
+ return OK;
+}
+
+int UDPSocketLibevent::SetMulticastLoopbackMode(bool loopback) {
+ DCHECK(CalledOnValidThread());
+ if (is_connected())
+ return ERR_UNEXPECTED;
+
+ if (loopback)
+ socket_options_ |= SOCKET_OPTION_MULTICAST_LOOP;
+ else
+ socket_options_ &= ~SOCKET_OPTION_MULTICAST_LOOP;
+ return OK;
+}
} // namespace net
diff --git a/net/udp/udp_socket_libevent.h b/net/udp/udp_socket_libevent.h
index ce8a0f2..ceec17e 100644
--- a/net/udp/udp_socket_libevent.h
+++ b/net/udp/udp_socket_libevent.h
@@ -113,12 +113,51 @@ class NET_EXPORT UDPSocketLibevent : public base::NonThreadSafe {
// called before Bind().
void AllowBroadcast();
+ // Join the multicast group.
+ // |group_address| is the group address to join, could be either
+ // an IPv4 or IPv6 address.
+ // Return a network error code.
+ int JoinGroup(const IPAddressNumber& group_address) const;
+
+ // Leave the multicast group.
+ // |group_address| is the group address to leave, could be either
+ // an IPv4 or IPv6 address. If the socket hasn't joined the group,
+ // it will be ignored.
+ // It's optional to leave the multicast group before destroying
+ // the socket. It will be done by the OS.
+ // Return a network error code.
+ int LeaveGroup(const IPAddressNumber& group_address) const;
+
+ // Set the time-to-live option for UDP packets sent to the multicast
+ // group address. The default value of this option is 1.
+ // Cannot be negative or more than 255.
+ // Should be called before Bind().
+ // Return a network error code.
+ int SetMulticastTimeToLive(int time_to_live);
+
+ // Set the loopback flag for UDP socket. If this flag is true, the host
+ // will receive packets sent to the joined group from itself.
+ // The default value of this option is true.
+ // Should be called before Bind().
+ // Return a network error code.
+ //
+ // Note: the behavior of |SetMulticastLoopbackMode| is slightly
+ // different between Windows and Unix-like systems. The inconsistency only
+ // happens when there are more than one applications on the same host
+ // joined to the same multicast group while having different settings on
+ // multicast loopback mode. On Windows, the applications with loopback off
+ // will not RECEIVE the loopback packets; while on Unix-like systems, the
+ // applications with loopback off will not SEND the loopback packets to
+ // other applications on the same host. See MSDN: http://goo.gl/6vqbj
+ int SetMulticastLoopbackMode(bool loopback);
+
private:
static const int kInvalidSocket = -1;
enum SocketOptions {
- SOCKET_OPTION_REUSE_ADDRESS = 1 << 0,
- SOCKET_OPTION_BROADCAST = 1 << 1
+ SOCKET_OPTION_REUSE_ADDRESS = 1 << 0,
+ SOCKET_OPTION_BROADCAST = 1 << 1,
+ SOCKET_OPTION_MULTICAST_LOOP = 1 << 2
};
class ReadWatcher : public MessageLoopForIO::Watcher {
@@ -188,11 +227,16 @@ class NET_EXPORT UDPSocketLibevent : public base::NonThreadSafe {
int RandomBind(const IPEndPoint& address);
int socket_;
+ int addr_family_;
// Bitwise-or'd combination of SocketOptions. Specifies the set of
// options that should be applied to |socket_| before Bind().
int socket_options_;
+ // Multicast socket options cached for SetSocketOption.
+ // Cannot be used after Bind().
+ int multicast_time_to_live_;
+
// How to do source port binding, used only when UDPSocket is part of
// UDPClientSocket, since UDPServerSocket provides Bind.
DatagramSocket::BindType bind_type_;
diff --git a/net/udp/udp_socket_unittest.cc b/net/udp/udp_socket_unittest.cc
index 09a2806..d488bea 100644
--- a/net/udp/udp_socket_unittest.cc
+++ b/net/udp/udp_socket_unittest.cc
@@ -526,6 +526,60 @@ TEST_F(UDPSocketTest, CloseWithPendingRead) {
EXPECT_FALSE(callback.have_result());
}
+#if defined(OS_ANDROID)
+// Some Android devices do not support multicast socket.
+// The ones supporting multicast need WifiManager.MulitcastLock to enable it.
+// http://goo.gl/jjAk9
+#define MAYBE_JoinMulticastGroup DISABLED_JoinMulticastGroup
+#else
+#define MAYBE_JoinMulticastGroup JoinMulticastGroup
+#endif // defined(OS_ANDROID)
+
+TEST_F(UDPSocketTest, MAYBE_JoinMulticastGroup) {
+ const int kPort = 9999;
+ const char* const kGroup = "237.132.100.17";
+
+ IPEndPoint bind_address;
+ CreateUDPAddress("0.0.0.0", kPort, &bind_address);
+ IPAddressNumber group_ip;
+ EXPECT_TRUE(ParseIPLiteralToNumber(kGroup, &group_ip));
+
+ UDPSocket socket(DatagramSocket::DEFAULT_BIND,
+ RandIntCallback(),
+ NULL,
+ NetLog::Source());
+ EXPECT_EQ(OK, socket.Bind(bind_address));
+ EXPECT_EQ(OK, socket.JoinGroup(group_ip));
+ // Joining group multiple times.
+ EXPECT_NE(OK, socket.JoinGroup(group_ip));
+ EXPECT_EQ(OK, socket.LeaveGroup(group_ip));
+ // Leaving group multiple times.
+ EXPECT_NE(OK, socket.LeaveGroup(group_ip));
+
+ socket.Close();
+}
+
+TEST_F(UDPSocketTest, MulticastOptions) {
+ const int kPort = 9999;
+ IPEndPoint bind_address;
+ CreateUDPAddress("0.0.0.0", kPort, &bind_address);
+
+ UDPSocket socket(DatagramSocket::DEFAULT_BIND,
+ RandIntCallback(),
+ NULL,
+ NetLog::Source());
+ // Before binding.
+ EXPECT_EQ(OK, socket.SetMulticastLoopbackMode(false));
+ EXPECT_EQ(OK, socket.SetMulticastLoopbackMode(true));
+ EXPECT_EQ(OK, socket.SetMulticastTimeToLive(0));
+ EXPECT_EQ(OK, socket.SetMulticastTimeToLive(3));
+ EXPECT_NE(OK, socket.SetMulticastTimeToLive(-1));
+
+ EXPECT_EQ(OK, socket.Bind(bind_address));
+
+ socket.Close();
+}
+
} // namespace
} // namespace net
diff --git a/net/udp/udp_socket_win.cc b/net/udp/udp_socket_win.cc
index 5c6da9f..dd0f880 100644
--- a/net/udp/udp_socket_win.cc
+++ b/net/udp/udp_socket_win.cc
@@ -163,7 +163,9 @@ UDPSocketWin::UDPSocketWin(DatagramSocket::BindType bind_type,
net::NetLog* net_log,
const net::NetLog::Source& source)
: socket_(INVALID_SOCKET),
- socket_options_(0),
+ addr_family_(0),
+ socket_options_(SOCKET_OPTION_MULTICAST_LOOP),
+ multicast_time_to_live_(1),
bind_type_(bind_type),
rand_int_cb_(rand_int_cb),
recv_from_address_(NULL),
@@ -355,7 +357,8 @@ int UDPSocketWin::Bind(const IPEndPoint& address) {
}
int UDPSocketWin::CreateSocket(const IPEndPoint& address) {
- socket_ = WSASocket(address.GetSockAddrFamily(), SOCK_DGRAM, IPPROTO_UDP,
+ addr_family_ = address.GetSockAddrFamily();
+ socket_ = WSASocket(addr_family_, SOCK_DGRAM, IPPROTO_UDP,
NULL, 0, WSA_FLAG_OVERLAPPED);
if (socket_ == INVALID_SOCKET)
return MapSystemError(WSAGetLastError());
@@ -575,14 +578,36 @@ int UDPSocketWin::SetSocketOptions() {
reinterpret_cast<const char*>(&true_value),
sizeof(true_value));
if (rv < 0)
- return MapSystemError(errno);
+ return MapSystemError(WSAGetLastError());
}
if (socket_options_ & SOCKET_OPTION_BROADCAST) {
int rv = setsockopt(socket_, SOL_SOCKET, SO_BROADCAST,
reinterpret_cast<const char*>(&true_value),
sizeof(true_value));
if (rv < 0)
- return MapSystemError(errno);
+ return MapSystemError(WSAGetLastError());
+ }
+ if (!(socket_options_ & SOCKET_OPTION_MULTICAST_LOOP)) {
+ DWORD loop = 0;
+ int protocol_level =
+ addr_family_ == AF_INET ? IPPROTO_IP : IPPROTO_IPV6;
+ int option =
+ addr_family_ == AF_INET ? IP_MULTICAST_LOOP: IPV6_MULTICAST_LOOP;
+ int rv = setsockopt(socket_, protocol_level, option,
+ reinterpret_cast<const char*>(&loop), sizeof(loop));
+ if (rv < 0)
+ return MapSystemError(WSAGetLastError());
+ }
+ if (multicast_time_to_live_ != 1) {
+ DWORD hops = multicast_time_to_live_;
+ int protocol_level =
+ addr_family_ == AF_INET ? IPPROTO_IP : IPPROTO_IPV6;
+ int option =
+ addr_family_ == AF_INET ? IP_MULTICAST_TTL: IPV6_MULTICAST_HOPS;
+ int rv = setsockopt(socket_, protocol_level, option,
+ reinterpret_cast<const char*>(&hops), sizeof(hops));
+ if (rv < 0)
+ return MapSystemError(WSAGetLastError());
}
return OK;
}
@@ -614,4 +639,105 @@ bool UDPSocketWin::ReceiveAddressToIPEndpoint(IPEndPoint* address) const {
return address->FromSockAddr(storage.addr, storage.addr_len);
}
+int UDPSocketWin::JoinGroup(
+ const IPAddressNumber& group_address) const {
+ DCHECK(CalledOnValidThread());
+ if (!is_connected())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ switch (group_address.size()) {
+ case kIPv4AddressSize: {
+ if (addr_family_ != AF_INET)
+ return ERR_ADDRESS_INVALID;
+ ip_mreq mreq;
+ mreq.imr_interface.s_addr = INADDR_ANY;
+ memcpy(&mreq.imr_multiaddr, &group_address[0], kIPv4AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ reinterpret_cast<const char*>(&mreq),
+ sizeof(mreq));
+ if (rv)
+ return MapSystemError(WSAGetLastError());
+ return OK;
+ }
+ case kIPv6AddressSize: {
+ if (addr_family_ != AF_INET6)
+ return ERR_ADDRESS_INVALID;
+ ipv6_mreq mreq;
+ mreq.ipv6mr_interface = 0; // 0 indicates default multicast interface.
+ memcpy(&mreq.ipv6mr_multiaddr, &group_address[0], kIPv6AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP,
+ reinterpret_cast<const char*>(&mreq),
+ sizeof(mreq));
+ if (rv)
+ return MapSystemError(WSAGetLastError());
+ return OK;
+ }
+ default:
+ NOTREACHED() << "Invalid address family";
+ return ERR_ADDRESS_INVALID;
+ }
+}
+
+int UDPSocketWin::LeaveGroup(
+ const IPAddressNumber& group_address) const {
+ DCHECK(CalledOnValidThread());
+ if (!is_connected())
+ return ERR_SOCKET_NOT_CONNECTED;
+
+ switch (group_address.size()) {
+ case kIPv4AddressSize: {
+ if (addr_family_ != AF_INET)
+ return ERR_ADDRESS_INVALID;
+ ip_mreq mreq;
+ mreq.imr_interface.s_addr = INADDR_ANY;
+ memcpy(&mreq.imr_multiaddr, &group_address[0], kIPv4AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IP, IP_DROP_MEMBERSHIP,
+ reinterpret_cast<const char*>(&mreq),
+ sizeof(mreq));
+ if (rv)
+ return MapSystemError(WSAGetLastError());
+ return OK;
+ }
+ case kIPv6AddressSize: {
+ if (addr_family_ != AF_INET6)
+ return ERR_ADDRESS_INVALID;
+ ipv6_mreq mreq;
+ mreq.ipv6mr_interface = 0; // 0 indicates default multicast interface.
+ memcpy(&mreq.ipv6mr_multiaddr, &group_address[0], kIPv6AddressSize);
+ int rv = setsockopt(socket_, IPPROTO_IPV6, IP_DROP_MEMBERSHIP,
+ reinterpret_cast<const char*>(&mreq),
+ sizeof(mreq));
+ if (rv)
+ return MapSystemError(WSAGetLastError());
+ return OK;
+ }
+ default:
+ NOTREACHED() << "Invalid address family";
+ return ERR_ADDRESS_INVALID;
+ }
+}
+
+int UDPSocketWin::SetMulticastTimeToLive(int time_to_live) {
+ DCHECK(CalledOnValidThread());
+ if (is_connected())
+ return ERR_UNEXPECTED;
+
+ if (time_to_live < 0 || time_to_live > 255)
+ return ERR_INVALID_ARGUMENT;
+ multicast_time_to_live_ = time_to_live;
+ return OK;
+}
+
+int UDPSocketWin::SetMulticastLoopbackMode(bool loopback) {
+ DCHECK(CalledOnValidThread());
+ if (is_connected())
+ return ERR_UNEXPECTED;
+
+ if (loopback)
+ socket_options_ |= SOCKET_OPTION_MULTICAST_LOOP;
+ else
+ socket_options_ &= ~SOCKET_OPTION_MULTICAST_LOOP;
+ return OK;
+}
+
} // namespace net
diff --git a/net/udp/udp_socket_win.h b/net/udp/udp_socket_win.h
index ce17050..807b403 100644
--- a/net/udp/udp_socket_win.h
+++ b/net/udp/udp_socket_win.h
@@ -115,10 +115,47 @@ class NET_EXPORT UDPSocketWin : NON_EXPORTED_BASE(public base::NonThreadSafe) {
// called before Bind().
void AllowBroadcast();
+ // Join the multicast group.
+ // |group_address| is the group address to join, could be either
+ // an IPv4 or IPv6 address.
+ // Return a network error code.
+ int JoinGroup(const IPAddressNumber& group_address) const;
+
+ // Leave the multicast group.
+ // |group_address| is the group address to leave, could be either
+ // an IPv4 or IPv6 address. If the socket hasn't joined the group,
+ // it will be ignored.
+ // It's optional to leave the multicast group before destroying
+ // the socket. It will be done by the OS.
+ // Return a network error code.
+ int LeaveGroup(const IPAddressNumber& group_address) const;
+
+ // Set the time-to-live option for UDP packets sent to the multicast
+ // group address. The default value of this option is 1.
+ // Cannot be negative or more than 255.
+ // Should be called before Bind().
+ int SetMulticastTimeToLive(int time_to_live);
+
+ // Set the loopback flag for UDP socket. If this flag is true, the host
+ // will receive packets sent to the joined group from itself.
+ // The default value of this option is true.
+ // Should be called before Bind().
+ //
+ // Note: the behavior of |SetMulticastLoopbackMode| is slightly
+ // different between Windows and Unix-like systems. The inconsistency only
+ // happens when there are more than one applications on the same host
+ // joined to the same multicast group while having different settings on
+ // multicast loopback mode. On Windows, the applications with loopback off
+ // will not RECEIVE the loopback packets; while on Unix-like systems, the
+ // applications with loopback off will not SEND the loopback packets to
+ // other applications on the same host. See MSDN: http://goo.gl/6vqbj
+ int SetMulticastLoopbackMode(bool loopback);
+
private:
enum SocketOptions {
- SOCKET_OPTION_REUSE_ADDRESS = 1 << 0,
- SOCKET_OPTION_BROADCAST = 1 << 1
+ SOCKET_OPTION_REUSE_ADDRESS = 1 << 0,
+ SOCKET_OPTION_BROADCAST = 1 << 1,
+ SOCKET_OPTION_MULTICAST_LOOP = 1 << 2
};
class Core;
@@ -160,11 +197,16 @@ class NET_EXPORT UDPSocketWin : NON_EXPORTED_BASE(public base::NonThreadSafe) {
bool ReceiveAddressToIPEndpoint(IPEndPoint* address) const;
SOCKET socket_;
+ int addr_family_;
// Bitwise-or'd combination of SocketOptions. Specifies the set of
// options that should be applied to |socket_| before Bind().
int socket_options_;
+ // Multicast socket options cached for SetSocketOption.
+ // Cannot be used after Bind().
+ int multicast_time_to_live_;
+
// How to do source port binding, used only when UDPSocket is part of
// UDPClientSocket, since UDPServerSocket provides Bind.
DatagramSocket::BindType bind_type_;