diff --git a/webrtc/BUILD.gn b/webrtc/BUILD.gn index d017b76c6c..2becbc74ea 100644 --- a/webrtc/BUILD.gn +++ b/webrtc/BUILD.gn @@ -481,6 +481,7 @@ if (rtc_include_tests) { "p2p/base/transportdescriptionfactory_unittest.cc", "p2p/base/turnport_unittest.cc", "p2p/base/turnserver_unittest.cc", + "p2p/base/udptransportchannel_unittest.cc", "p2p/client/basicportallocator_unittest.cc", "p2p/stunprober/stunprober_unittest.cc", ] diff --git a/webrtc/p2p/BUILD.gn b/webrtc/p2p/BUILD.gn index c196181078..ee796b06a6 100644 --- a/webrtc/p2p/BUILD.gn +++ b/webrtc/p2p/BUILD.gn @@ -74,6 +74,8 @@ rtc_static_library("rtc_p2p") { "base/turnport.cc", "base/turnport.h", "base/udpport.h", + "base/udptransportchannel.cc", + "base/udptransportchannel.h", "client/basicportallocator.cc", "client/basicportallocator.h", "client/socketmonitor.cc", diff --git a/webrtc/p2p/base/udptransportchannel.cc b/webrtc/p2p/base/udptransportchannel.cc new file mode 100644 index 0000000000..7360217778 --- /dev/null +++ b/webrtc/p2p/base/udptransportchannel.cc @@ -0,0 +1,144 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/p2p/base/udptransportchannel.h" + +#include + +#include "webrtc/base/asyncudpsocket.h" +#include "webrtc/base/asyncpacketsocket.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/thread_checker.h" +#include "webrtc/p2p/base/basicpacketsocketfactory.h" +#include "webrtc/p2p/base/packettransportinterface.h" + +namespace cricket { + +UdpTransportChannel::UdpTransportChannel(const std::string& transport_name) + : UdpTransportChannel(transport_name, + rtc::Thread::Current()->socketserver()) {} + +UdpTransportChannel::UdpTransportChannel(const std::string& transport_name, + rtc::SocketServer* socket_server) + : transport_name_(transport_name), socket_server_(socket_server) {} + +UdpTransportChannel::~UdpTransportChannel() { + RTC_DCHECK_RUN_ON(&network_thread_checker_); +} + +void UdpTransportChannel::OnSocketReadPacket( + rtc::AsyncPacketSocket* socket, + const char* data, + size_t len, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time) { + // No thread_checker in high frequency network function. + SignalReadPacket(this, data, len, packet_time, 0); +} + +void UdpTransportChannel::OnSocketSentPacket(rtc::AsyncPacketSocket* socket, + const rtc::SentPacket& packet) { + RTC_DCHECK_EQ(socket_.get(), socket); + SignalSentPacket(this, packet); +} + +bool UdpTransportChannel::writable() const { + return state_ == State::CONNECTED; +} + +int UdpTransportChannel::SendPacket(const char* data, + size_t len, + const rtc::PacketOptions& options, + int flags) { + // No thread_checker in high frequency network function. + if (!remote_parameters_) { + LOG(LS_WARNING) << "Remote parameters not set."; + send_error_ = ENOTCONN; + return -1; + } + const rtc::SocketAddress& remote_addr_ = *remote_parameters_; + int result = socket_->SendTo((const void*)data, len, remote_addr_, options); + if (result <= 0) { + LOG(LS_VERBOSE) << "SendPacket() " << result; + } + return result; +} + +void UdpTransportChannel::Start() { + RTC_DCHECK_RUN_ON(&network_thread_checker_); + if (socket_) { + LOG(LS_WARNING) << "Local socket already allocated."; + return; + } + static constexpr uint16_t kMaxTries = 100; + static constexpr uint16_t kMinPortNumber = 2000; + // TODO(johan) provide configuration option for kMinPortNumber. + rtc::SocketAddress socket_addr("0.0.0.0", 0); + // TODO(johan): Replace BasicPacketSocketFactory by something that honors RFC + // 3550 Section 11 port number requirements like + // {port_{RTP} is even, port_{RTCP} := port{RTP} + 1}. + rtc::BasicPacketSocketFactory socket_factory(socket_server_); + socket_.reset(socket_factory.CreateUdpSocket(socket_addr, kMinPortNumber, + kMinPortNumber + kMaxTries)); + if (socket_) { + local_parameters_ = + rtc::Optional(socket_->GetLocalAddress()); + LOG(INFO) << "Created UDP socket with addr " << local_parameters_->ipaddr() + << " port " << local_parameters_->port() << "."; + socket_->SignalReadPacket.connect(this, + &UdpTransportChannel::OnSocketReadPacket); + socket_->SignalSentPacket.connect(this, + &UdpTransportChannel::OnSocketSentPacket); + } else { + LOG(INFO) << "Local socket allocation failure"; + } + UpdateState(); + return; +} + +void UdpTransportChannel::UpdateState() { + RTC_DCHECK_RUN_ON(&network_thread_checker_); + RTC_DCHECK(!(local_parameters_ && !socket_)); + RTC_DCHECK(!(!local_parameters_ && socket_)); + if (!local_parameters_) { + SetState(State::INIT); + } else if (!remote_parameters_) { + SetState(State::CONNECTING); + } else { + SetState(State::CONNECTED); + } +} + +void UdpTransportChannel::SetRemoteParameters(const rtc::SocketAddress& addr) { + RTC_DCHECK_RUN_ON(&network_thread_checker_); + if (!addr.IsComplete()) { + LOG(INFO) << "remote address not complete"; + return; + } + // TODO(johan) check for ipv4, other settings. + remote_parameters_ = rtc::Optional(addr); + UpdateState(); +} + +void UdpTransportChannel::SetState(State state) { + RTC_DCHECK_RUN_ON(&network_thread_checker_); + if (state_ == state) { + return; + } + state_ = state; + if (state == State::CONNECTED) { + SignalWritableState(this); + SignalReadyToSend(this); + } +} +} // namespace cricket diff --git a/webrtc/p2p/base/udptransportchannel.h b/webrtc/p2p/base/udptransportchannel.h new file mode 100644 index 0000000000..2152fb7308 --- /dev/null +++ b/webrtc/p2p/base/udptransportchannel.h @@ -0,0 +1,96 @@ +/* + * Copyright 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_P2P_BASE_UDPTRANSPORTCHANNEL_H_ +#define WEBRTC_P2P_BASE_UDPTRANSPORTCHANNEL_H_ + +#include +#include + +#include "webrtc/base/optional.h" +#include "webrtc/base/thread_checker.h" +#include "webrtc/p2p/base/packettransportinterface.h" + +namespace rtc { +class AsyncPacketSocket; +class PhysicalSocketServer; +class SocketAddress; +class SocketServer; +class Thread; +} + +namespace cricket { + +class UdpTransportChannel : public rtc::PacketTransportInterface { + public: + enum class State { INIT, CONNECTING, CONNECTED, FAILED }; + explicit UdpTransportChannel(const std::string& transport_name); + UdpTransportChannel(const std::string& transport_name, rtc::SocketServer* ss); + ~UdpTransportChannel(); + + const std::string debug_name() const override { return transport_name_; } + + bool receiving() const override { + // TODO(johan): Implement method and signal. + return true; + } + + bool writable() const override; + + int SendPacket(const char* data, + size_t len, + const rtc::PacketOptions& options, + int flags) override; + + int SetOption(rtc::Socket::Option opt, int value) override { return 0; } + + int GetError() override { return send_error_; } + + State state() const { + RTC_DCHECK_RUN_ON(&network_thread_checker_); + return state_; + } + + // Start() makes UdpTransportChannel transition from state INIT to CONNECTING. + // It creates the local UDP socket and binds it to a port. + // Consider checking state() after calling Start(). + void Start(); + + void SetRemoteParameters(const rtc::SocketAddress& remote); + + // Returned optional does not have a value if in the INIT or FAILED state. + // Consider checking state() before calling local_parameters(). + rtc::Optional local_parameters() const { + return local_parameters_; + } + + private: + void OnSocketReadPacket(rtc::AsyncPacketSocket* socket, + const char* data, + size_t len, + const rtc::SocketAddress& remote_addr, + const rtc::PacketTime& packet_time); + void OnSocketSentPacket(rtc::AsyncPacketSocket* socket, + const rtc::SentPacket& packet); + void SetState(State state); // Set State and Signal. + bool IsLocalConsistent(); + void UpdateState(); + std::string transport_name_; + rtc::SocketServer* socket_server_; + State state_ = State::INIT; + int send_error_ = 0; + std::unique_ptr socket_; + rtc::Optional local_parameters_; + rtc::Optional remote_parameters_; + rtc::ThreadChecker network_thread_checker_; +}; +} // namespace cricket + +#endif // WEBRTC_P2P_BASE_UDPTRANSPORTCHANNEL_H_ diff --git a/webrtc/p2p/base/udptransportchannel_unittest.cc b/webrtc/p2p/base/udptransportchannel_unittest.cc new file mode 100644 index 0000000000..1cedde6472 --- /dev/null +++ b/webrtc/p2p/base/udptransportchannel_unittest.cc @@ -0,0 +1,185 @@ +/* + * Copyright 2016 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include +#include +#include + +#include "webrtc/base/gunit.h" +#include "webrtc/base/thread.h" +#include "webrtc/base/asyncpacketsocket.h" +#include "webrtc/base/ipaddress.h" +#include "webrtc/base/physicalsocketserver.h" +#include "webrtc/base/socketaddress.h" +#include "webrtc/base/socketserver.h" +#include "webrtc/base/virtualsocketserver.h" +#include "webrtc/p2p/base/packettransportinterface.h" +#include "webrtc/p2p/base/udptransportchannel.h" + +namespace cricket { + +constexpr int kTimeoutMs = 10000; +static const rtc::IPAddress kIPv4LocalHostAddress = + rtc::IPAddress(0x7F000001); // 127.0.0.1 + +class UdpTransportChannelTest : public testing::Test, + public sigslot::has_slots<> { + public: + UdpTransportChannelTest() + : network_thread_(rtc::Thread::Current()), + physical_socket_server_(new rtc::PhysicalSocketServer), + virtual_socket_server_( + new rtc::VirtualSocketServer(physical_socket_server_.get())), + ss_scope_(virtual_socket_server_.get()), + ep1_("Name1"), + ep2_("name2") { + // Setup IP Address for outgoing packets from sockets bound to + // IPV4 INADDR_ANY ("0.0.0.0."). Virtual socket server sends these packets + // only if the default address is explicit set. With a physical socket, the + // actual network stack / operating system would set the IP address for + // outgoing packets. + virtual_socket_server_->SetDefaultRoute(kIPv4LocalHostAddress); + } + + struct Endpoint : public sigslot::has_slots<> { + explicit Endpoint(std::string tch_name) { + ch_.reset(new UdpTransportChannel(std::move(tch_name))); + ch_->SignalReadPacket.connect(this, &Endpoint::OnReadPacket); + ch_->SignalSentPacket.connect(this, &Endpoint::OnSentPacket); + ch_->SignalReadyToSend.connect(this, &Endpoint::OnReadyToSend); + ch_->SignalWritableState.connect(this, &Endpoint::OnWritableState); + } + + bool CheckData(const char* data, int len) { + bool ret = false; + if (!ch_packets_.empty()) { + std::string packet = ch_packets_.front(); + ret = (packet == std::string(data, len)); + ch_packets_.pop_front(); + } + return ret; + } + + void OnWritableState(rtc::PacketTransportInterface* transport) { + num_sig_writable_++; + } + + void OnReadyToSend(rtc::PacketTransportInterface* transport) { + num_sig_ready_to_send_++; + } + + void OnReadPacket(rtc::PacketTransportInterface* transport, + const char* data, + size_t len, + const rtc::PacketTime& packet_time, + int flags) { + num_received_packets_++; + LOG(LS_VERBOSE) << "OnReadPacket (unittest)"; + ch_packets_.push_front(std::string(data, len)); + } + + void OnSentPacket(rtc::PacketTransportInterface* transport, + const rtc::SentPacket&) { + num_sig_sent_packets_++; + } + + int SendData(const char* data, size_t len) { + rtc::PacketOptions options; + return ch_->SendPacket(data, len, options, 0); + } + + void GetLocalPort(uint16_t* local_port) { + rtc::Optional addr = ch_->local_parameters(); + if (!addr) { + *local_port = 0; + return; + } + *local_port = addr->port(); + } + + std::list ch_packets_; + std::unique_ptr ch_; + uint32_t num_received_packets_ = 0; // Increases on SignalReadPacket. + uint32_t num_sig_sent_packets_ = 0; // Increases on SignalSentPacket. + uint32_t num_sig_writable_ = 0; // Increases on SignalWritable. + uint32_t num_sig_ready_to_send_ = 0; // Increases on SignalReadyToSend. + }; + + rtc::Thread* network_thread_ = nullptr; + std::unique_ptr physical_socket_server_; + std::unique_ptr virtual_socket_server_; + rtc::SocketServerScope ss_scope_; + + Endpoint ep1_; + Endpoint ep2_; + + void TestSendRecv() { + for (uint32_t i = 0; i < 5; ++i) { + static const char* data = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; + int len = static_cast(strlen(data)); + // local_channel <==> remote_channel + EXPECT_EQ_WAIT(len, ep1_.SendData(data, len), kTimeoutMs); + EXPECT_TRUE_WAIT(ep2_.CheckData(data, len), kTimeoutMs); + EXPECT_EQ_WAIT(i + 1u, ep2_.num_received_packets_, kTimeoutMs); + EXPECT_EQ_WAIT(len, ep2_.SendData(data, len), kTimeoutMs); + EXPECT_TRUE_WAIT(ep1_.CheckData(data, len), kTimeoutMs); + EXPECT_EQ_WAIT(i + 1u, ep1_.num_received_packets_, kTimeoutMs); + } + } +}; + +TEST_F(UdpTransportChannelTest, SendRecvBasic) { + ep1_.ch_->Start(); + ep2_.ch_->Start(); + uint16_t port; + ep2_.GetLocalPort(&port); + rtc::SocketAddress addr2 = rtc::SocketAddress("127.0.0.1", port); + ep1_.ch_->SetRemoteParameters(addr2); + ep1_.GetLocalPort(&port); + rtc::SocketAddress addr1 = rtc::SocketAddress("127.0.0.1", port); + ep2_.ch_->SetRemoteParameters(addr1); + TestSendRecv(); +} + +TEST_F(UdpTransportChannelTest, DefaultLocalParameters) { + EXPECT_FALSE(ep1_.ch_->local_parameters()); +} + +TEST_F(UdpTransportChannelTest, StartTwice) { + ep1_.ch_->Start(); + EXPECT_EQ(UdpTransportChannel::State::CONNECTING, ep1_.ch_->state()); + ep1_.ch_->Start(); + EXPECT_EQ(UdpTransportChannel::State::CONNECTING, ep1_.ch_->state()); +} + +TEST_F(UdpTransportChannelTest, StatusAndSignals) { + EXPECT_EQ(UdpTransportChannel::State::INIT, ep1_.ch_->state()); + ep1_.ch_->Start(); + EXPECT_EQ(UdpTransportChannel::State::CONNECTING, ep1_.ch_->state()); + EXPECT_EQ(0u, ep1_.num_sig_writable_); + EXPECT_EQ(0u, ep1_.num_sig_ready_to_send_); + // Loopback + EXPECT_TRUE(!ep1_.ch_->writable()); + rtc::Optional addr = ep1_.ch_->local_parameters(); + ASSERT_TRUE(addr); + // Keep port, but explicitly set IP. + addr->SetIP("127.0.0.1"); + ep1_.ch_->SetRemoteParameters(*addr); + EXPECT_TRUE(ep1_.ch_->writable()); + EXPECT_EQ(1u, ep1_.num_sig_writable_); + EXPECT_EQ(1u, ep1_.num_sig_ready_to_send_); + const char data[] = "abc"; + ep1_.SendData(data, sizeof(data)); + EXPECT_EQ_WAIT(1u, ep1_.ch_packets_.size(), kTimeoutMs); + EXPECT_EQ_WAIT(1u, ep1_.num_sig_sent_packets_, kTimeoutMs); +} +} // namespace cricket diff --git a/webrtc/p2p/p2p.gyp b/webrtc/p2p/p2p.gyp index 19c022ff40..0e7c46b821 100644 --- a/webrtc/p2p/p2p.gyp +++ b/webrtc/p2p/p2p.gyp @@ -70,6 +70,8 @@ 'base/turnport.cc', 'base/turnport.h', 'base/udpport.h', + 'base/udptransportchannel.cc', + 'base/udptransportchannel.h', 'client/basicportallocator.cc', 'client/basicportallocator.h', 'client/socketmonitor.cc',