diff options
Diffstat (limited to 'net')
-rw-r--r-- | net/udp/udp_socket_libevent.cc | 137 | ||||
-rw-r--r-- | net/udp/udp_socket_libevent.h | 48 | ||||
-rw-r--r-- | net/udp/udp_socket_unittest.cc | 54 | ||||
-rw-r--r-- | net/udp/udp_socket_win.cc | 134 | ||||
-rw-r--r-- | net/udp/udp_socket_win.h | 46 |
5 files changed, 406 insertions, 13 deletions
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_; |