diff options
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, + ¶m)) { + 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, + ¶m)) { + 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, + ¶m)) { + 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_; |