299 lines
9.3 KiB
C
299 lines
9.3 KiB
C
/*
|
|
* Author: Christian Huitema
|
|
* Copyright (c) 2017, Private Octopus, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* Really basic network simulator, only simulates a simple link using a
|
|
* packet structure.
|
|
* Init: link creation. Returns a link structure with defined bandwidth,
|
|
* latency, loss pattern and initial time. The link is empty. The loss
|
|
* pattern is a 64 bit bit mask.
|
|
* Submit packet of length L at time t. The packet is queued to the link.
|
|
* Get packet out of link at time T + L + Queue.
|
|
*/
|
|
|
|
#include "picoquic_internal.h"
|
|
#include "picoquic_utils.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
picoquictest_sim_link_t* picoquictest_sim_link_create(double data_rate_in_gps,
|
|
uint64_t microsec_latency, uint64_t* loss_mask, uint64_t queue_delay_max, uint64_t current_time)
|
|
{
|
|
picoquictest_sim_link_t* link = (picoquictest_sim_link_t*)malloc(sizeof(picoquictest_sim_link_t));
|
|
if (link != 0) {
|
|
double pico_d = (data_rate_in_gps <= 0) ? 0 : (8000.0 / data_rate_in_gps);
|
|
pico_d *= (1.024 * 1.024); /* account for binary units */
|
|
link->next_send_time = current_time;
|
|
link->queue_time = current_time;
|
|
link->queue_delay_max = queue_delay_max;
|
|
link->picosec_per_byte = (uint64_t)pico_d;
|
|
link->microsec_latency = microsec_latency;
|
|
link->packets_dropped = 0;
|
|
link->packets_sent = 0;
|
|
link->first_packet = NULL;
|
|
link->last_packet = NULL;
|
|
link->loss_mask = loss_mask;
|
|
link->jitter_seed = 0xDEADBEEFBABAC001ull;
|
|
link->jitter = 0;
|
|
link->path_mtu = PICOQUIC_MAX_PACKET_SIZE;
|
|
link->red_drop_mask = 0;
|
|
link->red_queue_max = 0;
|
|
link->bucket_increase_per_microsec = 0;
|
|
link->bucket_max = 0;
|
|
link->bucket_current = 0;
|
|
link->bucket_arrival_last = current_time;
|
|
}
|
|
|
|
return link;
|
|
}
|
|
|
|
void picoquictest_sim_link_delete(picoquictest_sim_link_t* link)
|
|
{
|
|
picoquictest_sim_packet_t* packet;
|
|
|
|
while ((packet = link->first_packet) != NULL) {
|
|
link->first_packet = packet->next_packet;
|
|
free(packet);
|
|
}
|
|
|
|
free(link);
|
|
}
|
|
|
|
picoquictest_sim_packet_t* picoquictest_sim_link_create_packet()
|
|
{
|
|
picoquictest_sim_packet_t* packet = (picoquictest_sim_packet_t*)malloc(sizeof(picoquictest_sim_packet_t));
|
|
if (packet != NULL) {
|
|
packet->next_packet = NULL;
|
|
packet->arrival_time = 0;
|
|
packet->length = 0;
|
|
}
|
|
|
|
return packet;
|
|
}
|
|
|
|
uint64_t picoquictest_sim_link_next_arrival(picoquictest_sim_link_t* link, uint64_t current_time)
|
|
{
|
|
picoquictest_sim_packet_t* packet = link->first_packet;
|
|
|
|
if (packet != NULL && packet->arrival_time < current_time) {
|
|
current_time = packet->arrival_time;
|
|
}
|
|
|
|
return current_time;
|
|
}
|
|
|
|
picoquictest_sim_packet_t* picoquictest_sim_link_dequeue(picoquictest_sim_link_t* link,
|
|
uint64_t current_time)
|
|
{
|
|
picoquictest_sim_packet_t* packet = link->first_packet;
|
|
|
|
if (packet != NULL && packet->arrival_time <= current_time) {
|
|
link->first_packet = packet->next_packet;
|
|
if (link->first_packet == NULL) {
|
|
link->last_packet = NULL;
|
|
}
|
|
} else {
|
|
packet = NULL;
|
|
}
|
|
|
|
return packet;
|
|
}
|
|
|
|
static int picoquictest_sim_link_testloss(uint64_t* loss_mask)
|
|
{
|
|
uint64_t loss_bit = 0;
|
|
|
|
if (loss_mask != NULL) {
|
|
/* Last bit indicates loss or not */
|
|
loss_bit = (uint64_t)((*loss_mask) & 1ull);
|
|
|
|
/* Rotate loss mask by 1 to prepare next round */
|
|
*loss_mask >>= 1;
|
|
*loss_mask |= (loss_bit << 63);
|
|
}
|
|
|
|
return (int)loss_bit;
|
|
}
|
|
|
|
static uint64_t picoquictest_sim_link_jitter(picoquictest_sim_link_t* link)
|
|
{
|
|
uint64_t jitter = link->jitter;
|
|
double x = picoquic_test_gauss_random(&link->jitter_seed);
|
|
if (x < -3.0) {
|
|
x = -3.0;
|
|
}
|
|
x /= 3.0;
|
|
jitter += (int64_t)(x * (double)jitter);
|
|
|
|
return jitter;
|
|
}
|
|
|
|
void picoquictest_sim_link_submit(picoquictest_sim_link_t* link, picoquictest_sim_packet_t* packet,
|
|
uint64_t current_time)
|
|
{
|
|
uint64_t queue_delay = (current_time > link->queue_time) ? 0 : link->queue_time - current_time;
|
|
uint64_t transmit_time = ((link->picosec_per_byte * ((uint64_t)packet->length)) >> 20);
|
|
uint64_t should_drop = 0;
|
|
|
|
if (transmit_time <= 0)
|
|
transmit_time = 1;
|
|
|
|
if (link->bucket_increase_per_microsec > 0) {
|
|
/* Simulate a rate limiter based on classic leaky bucket algorithm */
|
|
uint64_t delta_microsec = current_time - link->bucket_arrival_last;
|
|
link->bucket_arrival_last = current_time;
|
|
link->bucket_current += ((double)delta_microsec) * link->bucket_increase_per_microsec;
|
|
if (link->bucket_current > (double)link->bucket_max) {
|
|
link->bucket_current = (double)link->bucket_max;
|
|
}
|
|
if (link->bucket_current > (double)packet->length) {
|
|
link->bucket_current -= (double)packet->length;
|
|
}
|
|
else {
|
|
should_drop = 1;
|
|
}
|
|
} else if (link->queue_delay_max > 0 && queue_delay >= link->queue_delay_max) {
|
|
if (link->red_drop_mask == 0 || queue_delay >= link->red_queue_max) {
|
|
should_drop = 1;
|
|
}
|
|
else {
|
|
should_drop = link->red_drop_mask & 1;
|
|
link->red_drop_mask >>= 1;
|
|
link->red_drop_mask |= (should_drop << 63);
|
|
}
|
|
}
|
|
|
|
if (!should_drop) {
|
|
|
|
link->queue_time = current_time + queue_delay + transmit_time;
|
|
|
|
if (packet->length > link->path_mtu || picoquictest_sim_link_testloss(link->loss_mask) != 0) {
|
|
link->packets_dropped++;
|
|
free(packet);
|
|
} else {
|
|
link->packets_sent++;
|
|
if (link->last_packet == NULL) {
|
|
link->first_packet = packet;
|
|
} else {
|
|
link->last_packet->next_packet = packet;
|
|
}
|
|
link->last_packet = packet;
|
|
packet->next_packet = NULL;
|
|
packet->arrival_time = link->queue_time + link->microsec_latency;
|
|
if (link->jitter != 0) {
|
|
packet->arrival_time += picoquictest_sim_link_jitter(link);
|
|
}
|
|
}
|
|
} else {
|
|
/* simulate congestion loss or random drop on queue full */
|
|
link->packets_dropped++;
|
|
free(packet);
|
|
}
|
|
}
|
|
|
|
int sim_link_one_test(uint64_t* loss_mask, uint64_t queue_delay_max, uint64_t nb_losses)
|
|
{
|
|
int ret = 0;
|
|
uint64_t current_time = 0;
|
|
uint64_t departure_time = 0;
|
|
picoquictest_sim_link_t* link = picoquictest_sim_link_create(0.01, 10000, loss_mask, queue_delay_max, current_time);
|
|
uint64_t dequeued = 0;
|
|
uint64_t queued = 0;
|
|
const uint64_t nb_packets = 16;
|
|
|
|
if (link == NULL) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
|
|
while (ret == 0) {
|
|
if (queued >= nb_packets) {
|
|
departure_time = (uint64_t)((int64_t)-1);
|
|
}
|
|
|
|
current_time = picoquictest_sim_link_next_arrival(link, departure_time);
|
|
|
|
picoquictest_sim_packet_t* packet = picoquictest_sim_link_dequeue(link, current_time);
|
|
|
|
if (packet != NULL) {
|
|
dequeued++;
|
|
free(packet);
|
|
}
|
|
else if (queued < nb_packets) {
|
|
packet = picoquictest_sim_link_create_packet();
|
|
|
|
if (packet == NULL) {
|
|
ret = -1;
|
|
}
|
|
else {
|
|
packet->length = sizeof(packet->bytes);
|
|
picoquictest_sim_link_submit(link, packet, departure_time);
|
|
departure_time += 250;
|
|
queued++;
|
|
}
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ((dequeued + nb_losses) != nb_packets) {
|
|
ret = -1;
|
|
}
|
|
|
|
picoquictest_sim_link_delete(link);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int sim_link_test()
|
|
{
|
|
int ret = 0;
|
|
uint64_t loss_mask = 0;
|
|
|
|
ret = sim_link_one_test(&loss_mask, 0, 0);
|
|
|
|
if (ret == 0) {
|
|
loss_mask = 8;
|
|
ret = sim_link_one_test(&loss_mask, 0, 1);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
loss_mask = 0x18;
|
|
ret = sim_link_one_test(&loss_mask, 0, 2);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void picoquic_set_test_address(struct sockaddr_in * addr, uint32_t addr_val, uint16_t port)
|
|
{
|
|
/* Init of the IP addresses */
|
|
memset(addr, 0, sizeof(struct sockaddr_in));
|
|
addr->sin_family = AF_INET;
|
|
#ifdef _WINDOWS
|
|
addr->sin_addr.S_un.S_addr = addr_val;
|
|
#else
|
|
addr->sin_addr.s_addr = addr_val;
|
|
#endif
|
|
addr->sin_port = port;
|
|
} |