This CL contains network emulation layer and is a first part of landing CL https://webrtc-review.googlesource.com/c/src/+/116663 Bug: webrtc:10138 Change-Id: If664b21e9df847aef8144d622d08fc7e9f6608da Reviewed-on: https://webrtc-review.googlesource.com/c/120406 Commit-Queue: Artem Titov <titovartem@webrtc.org> Reviewed-by: Sebastian Jansson <srte@webrtc.org> Reviewed-by: Karl Wiberg <kwiberg@webrtc.org> Cr-Commit-Position: refs/heads/master@{#26470}
240 lines
8.0 KiB
C++
240 lines
8.0 KiB
C++
/*
|
|
* Copyright (c) 2018 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 "test/scenario/network/network_emulation.h"
|
|
|
|
#include <limits>
|
|
#include <memory>
|
|
|
|
#include "absl/memory/memory.h"
|
|
#include "rtc_base/bind.h"
|
|
#include "rtc_base/logging.h"
|
|
|
|
namespace webrtc {
|
|
namespace test {
|
|
|
|
EmulatedIpPacket::EmulatedIpPacket(const rtc::SocketAddress& from,
|
|
const rtc::SocketAddress& to,
|
|
uint64_t dest_endpoint_id,
|
|
rtc::CopyOnWriteBuffer data,
|
|
Timestamp arrival_time)
|
|
: from(from),
|
|
to(to),
|
|
dest_endpoint_id(dest_endpoint_id),
|
|
data(data),
|
|
arrival_time(arrival_time) {}
|
|
EmulatedIpPacket::~EmulatedIpPacket() = default;
|
|
EmulatedIpPacket::EmulatedIpPacket(EmulatedIpPacket&&) = default;
|
|
EmulatedIpPacket& EmulatedIpPacket::operator=(EmulatedIpPacket&&) = default;
|
|
|
|
void EmulatedNetworkNode::CreateRoute(
|
|
uint64_t receiver_id,
|
|
std::vector<EmulatedNetworkNode*> nodes,
|
|
EmulatedNetworkReceiverInterface* receiver) {
|
|
RTC_CHECK(!nodes.empty());
|
|
for (size_t i = 0; i + 1 < nodes.size(); ++i)
|
|
nodes[i]->SetReceiver(receiver_id, nodes[i + 1]);
|
|
nodes.back()->SetReceiver(receiver_id, receiver);
|
|
}
|
|
|
|
void EmulatedNetworkNode::ClearRoute(uint64_t receiver_id,
|
|
std::vector<EmulatedNetworkNode*> nodes) {
|
|
for (EmulatedNetworkNode* node : nodes)
|
|
node->RemoveReceiver(receiver_id);
|
|
}
|
|
|
|
EmulatedNetworkNode::EmulatedNetworkNode(
|
|
std::unique_ptr<NetworkBehaviorInterface> network_behavior)
|
|
: network_behavior_(std::move(network_behavior)) {}
|
|
|
|
EmulatedNetworkNode::~EmulatedNetworkNode() = default;
|
|
|
|
void EmulatedNetworkNode::OnPacketReceived(EmulatedIpPacket packet) {
|
|
rtc::CritScope crit(&lock_);
|
|
if (routing_.find(packet.dest_endpoint_id) == routing_.end()) {
|
|
return;
|
|
}
|
|
uint64_t packet_id = next_packet_id_++;
|
|
bool sent = network_behavior_->EnqueuePacket(
|
|
PacketInFlightInfo(packet.size(), packet.arrival_time.us(), packet_id));
|
|
if (sent) {
|
|
packets_.emplace_back(StoredPacket{packet_id, std::move(packet), false});
|
|
}
|
|
}
|
|
|
|
void EmulatedNetworkNode::Process(Timestamp at_time) {
|
|
std::vector<PacketDeliveryInfo> delivery_infos;
|
|
{
|
|
rtc::CritScope crit(&lock_);
|
|
absl::optional<int64_t> delivery_us =
|
|
network_behavior_->NextDeliveryTimeUs();
|
|
if (delivery_us && *delivery_us > at_time.us())
|
|
return;
|
|
|
|
delivery_infos = network_behavior_->DequeueDeliverablePackets(at_time.us());
|
|
}
|
|
for (PacketDeliveryInfo& delivery_info : delivery_infos) {
|
|
StoredPacket* packet = nullptr;
|
|
EmulatedNetworkReceiverInterface* receiver = nullptr;
|
|
{
|
|
rtc::CritScope crit(&lock_);
|
|
for (auto& stored_packet : packets_) {
|
|
if (stored_packet.id == delivery_info.packet_id) {
|
|
packet = &stored_packet;
|
|
break;
|
|
}
|
|
}
|
|
RTC_CHECK(packet);
|
|
RTC_DCHECK(!packet->removed);
|
|
receiver = routing_[packet->packet.dest_endpoint_id];
|
|
packet->removed = true;
|
|
}
|
|
RTC_CHECK(receiver);
|
|
// We don't want to keep the lock here. Otherwise we would get a deadlock if
|
|
// the receiver tries to push a new packet.
|
|
if (delivery_info.receive_time_us != PacketDeliveryInfo::kNotReceived) {
|
|
packet->packet.arrival_time =
|
|
Timestamp::us(delivery_info.receive_time_us);
|
|
receiver->OnPacketReceived(std::move(packet->packet));
|
|
}
|
|
{
|
|
rtc::CritScope crit(&lock_);
|
|
while (!packets_.empty() && packets_.front().removed) {
|
|
packets_.pop_front();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void EmulatedNetworkNode::SetReceiver(
|
|
uint64_t dest_endpoint_id,
|
|
EmulatedNetworkReceiverInterface* receiver) {
|
|
rtc::CritScope crit(&lock_);
|
|
RTC_CHECK(routing_
|
|
.insert(std::pair<uint64_t, EmulatedNetworkReceiverInterface*>(
|
|
dest_endpoint_id, receiver))
|
|
.second)
|
|
<< "Routing for endpoint " << dest_endpoint_id << " already exists";
|
|
}
|
|
|
|
void EmulatedNetworkNode::RemoveReceiver(uint64_t dest_endpoint_id) {
|
|
rtc::CritScope crit(&lock_);
|
|
routing_.erase(dest_endpoint_id);
|
|
}
|
|
|
|
EndpointNode::EndpointNode(uint64_t id, rtc::IPAddress ip, Clock* clock)
|
|
: id_(id),
|
|
peer_local_addr_(ip),
|
|
send_node_(nullptr),
|
|
clock_(clock),
|
|
next_port_(kFirstEphemeralPort),
|
|
connected_endpoint_id_(absl::nullopt) {}
|
|
EndpointNode::~EndpointNode() = default;
|
|
|
|
uint64_t EndpointNode::GetId() const {
|
|
return id_;
|
|
}
|
|
|
|
void EndpointNode::SetSendNode(EmulatedNetworkNode* send_node) {
|
|
send_node_ = send_node;
|
|
}
|
|
|
|
void EndpointNode::SendPacket(const rtc::SocketAddress& from,
|
|
const rtc::SocketAddress& to,
|
|
rtc::CopyOnWriteBuffer packet) {
|
|
RTC_CHECK(from.ipaddr() == peer_local_addr_);
|
|
RTC_CHECK(connected_endpoint_id_);
|
|
RTC_CHECK(send_node_);
|
|
send_node_->OnPacketReceived(EmulatedIpPacket(
|
|
from, to, connected_endpoint_id_.value(), std::move(packet),
|
|
Timestamp::us(clock_->TimeInMicroseconds())));
|
|
}
|
|
|
|
absl::optional<uint16_t> EndpointNode::BindReceiver(
|
|
uint16_t desired_port,
|
|
EmulatedNetworkReceiverInterface* receiver) {
|
|
rtc::CritScope crit(&receiver_lock_);
|
|
uint16_t port = desired_port;
|
|
if (port == 0) {
|
|
// Because client can specify its own port, next_port_ can be already in
|
|
// use, so we need to find next available port.
|
|
int ports_pool_size =
|
|
std::numeric_limits<uint16_t>::max() - kFirstEphemeralPort + 1;
|
|
for (int i = 0; i < ports_pool_size; ++i) {
|
|
uint16_t next_port = NextPort();
|
|
if (port_to_receiver_.find(next_port) == port_to_receiver_.end()) {
|
|
port = next_port;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
RTC_CHECK(port != 0) << "Can't find free port for receiver in endpoint "
|
|
<< id_;
|
|
bool result = port_to_receiver_.insert({port, receiver}).second;
|
|
if (!result) {
|
|
RTC_LOG(INFO) << "Can't bind receiver to used port " << desired_port
|
|
<< " in endpoint " << id_;
|
|
return absl::nullopt;
|
|
}
|
|
RTC_LOG(INFO) << "New receiver is binded to endpoint " << id_ << " on port "
|
|
<< port;
|
|
return port;
|
|
}
|
|
|
|
uint16_t EndpointNode::NextPort() {
|
|
uint16_t out = next_port_;
|
|
if (next_port_ == std::numeric_limits<uint16_t>::max()) {
|
|
next_port_ = kFirstEphemeralPort;
|
|
} else {
|
|
next_port_++;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
void EndpointNode::UnbindReceiver(uint16_t port) {
|
|
rtc::CritScope crit(&receiver_lock_);
|
|
port_to_receiver_.erase(port);
|
|
}
|
|
|
|
rtc::IPAddress EndpointNode::GetPeerLocalAddress() const {
|
|
return peer_local_addr_;
|
|
}
|
|
|
|
void EndpointNode::OnPacketReceived(EmulatedIpPacket packet) {
|
|
RTC_CHECK(packet.dest_endpoint_id == id_)
|
|
<< "Routing error: wrong destination endpoint. Destination id: "
|
|
<< packet.dest_endpoint_id << "; Receiver id: " << id_;
|
|
rtc::CritScope crit(&receiver_lock_);
|
|
auto it = port_to_receiver_.find(packet.to.port());
|
|
if (it == port_to_receiver_.end()) {
|
|
// It can happen, that remote peer closed connection, but there still some
|
|
// packets, that are going to it. It can happen during peer connection close
|
|
// process: one peer closed connection, second still sending data.
|
|
RTC_LOG(INFO) << "No receiver registered in " << id_ << " on port "
|
|
<< packet.to.port();
|
|
return;
|
|
}
|
|
// Endpoint assumes frequent calls to bind and unbind methods, so it holds
|
|
// lock during packet processing to ensure that receiver won't be deleted
|
|
// before call to OnPacketReceived.
|
|
it->second->OnPacketReceived(std::move(packet));
|
|
}
|
|
|
|
EmulatedNetworkNode* EndpointNode::GetSendNode() const {
|
|
return send_node_;
|
|
}
|
|
|
|
void EndpointNode::SetConnectedEndpointId(uint64_t endpoint_id) {
|
|
connected_endpoint_id_ = endpoint_id;
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace webrtc
|