summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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_;