Embracing Packet Loss in TCP Congestion Control
a network packet is a formatted unit of data carried by a packet-switched network. A packet consists of control information and user data; the latter is also known as the payload. Control information provides data for delivering the payload.
-
TCP (Transmission Control Protocol) is a standard that defines how to establish and maintain a network conversation through which application programs can exchange data. TCP works with the Internet Protocol (IP), which defines how computers send packets of data to each other.
-
Packet loss occurs when one or more packets of data travelling across a computer network fail to reach their destination. Packet loss is either caused by errors in data transmission, typically across wireless networks, or network congestion.
-
TCP uses a network congestion-avoidance algorithm that includes various aspects of an additive increase/multiplicative decrease scheme, along with other schemes including slow start and congestion window, to achieve congestion avoidance.
-
Reno: if three duplicate ACKs are received, Reno will perform a fast retransmit and skip the slow start phase by instead halving the congestion window, setting the slow start threshold equal to the new congestion window, and enter a phase called fast recovery.
-
Cubic: TCP CUBIC default in Linux, most popular TCP for popular Web servers.it’s increase TCP’s sending rate until packet loss occurs at some router’s output: the bottleneck link.
In this project I used these algorithms to measure how long it takes them to accurately detect the packet losses with creating sockets using TCP protocol and CC algorithms in c language. I also used ‘tc’ (Traffic Control) to packet loss of 10%, 15%, 20%, 25% and 30% by sending a 1MB file and here are the results:
Packet Loss | Cubic CC AVG Time | Reno CC AVG Time |
---|---|---|
0% | 1.002093 sec | 1.002233 sec |
10% | 1.084104 sec | 1.95328 sec |
15% | 1.084104 sec | 3.182702 sec |
20% | 6.844125 sec | 8.352214 sec |
25% | 18.392849 sec | 14.452283 sec |
30% | 42.182998 sec | 64.535568 sec |
sender.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#define SERVER_PORT 5060
#define SERVER_IP_ADDRESS "127.0.0.1"
#define FILENAME "1mb.txt"
#define FULL_SIZE 1048576
#define BUFF_SIZE 1500
int main() {
/* INIT FIELDS
* --> file_pointer == a pointer to the given file .
* --> server_address == a structure describing an internet socket address.
*/
char buffer[BUFF_SIZE];
int file_size;
FILE *file_pointer;
socklen_t length;
int number_of_runs = 0;
int i = 0;
while(i < 2) {
int j = 0;
while(j < 5) {
/* CREATE CLIENT SOCKET
* --> SOCK_STREAM == a TCP protocol type.
*/
int client_socket = socket(AF_INET, SOCK_STREAM, 0);
if(client_socket == -1) {
fprintf(stderr, "Couldn't create the socket : %s\n", strerror(errno));
exit(EXIT_FAILURE); // failing exit status.
}
/* CONGESTION CONTROL PROGRAM
* --> getsockopt == function manipulates options associated with a socket.
* --> setsockopt == function shall set the option specified by the option_name argument.
* --> IPPROTO_TCP == to indicate that an option is interpreted by the TCP.
* --> TCP_CONGESTION == Congestion control algorithm.
*/
int get_sock_opt = getsockopt(client_socket, IPPROTO_TCP, TCP_CONGESTION, buffer, &length);
if( get_sock_opt != 0) {
perror("getsockopt");
exit(EXIT_FAILURE); // failing exit status.
}
if(i == 0) {
strcpy(buffer,"cubic");
} else {
strcpy(buffer,"reno");
}
length = sizeof(buffer);
int set_sock_opt = setsockopt(client_socket, IPPROTO_TCP, TCP_CONGESTION, buffer, length);
if(set_sock_opt !=0 ) {
perror("setsockopt");
exit(EXIT_FAILURE); // failing exit status.
}
get_sock_opt = getsockopt(client_socket, IPPROTO_TCP, TCP_CONGESTION, buffer, &length);
if( get_sock_opt != 0) {
perror("getsockopt");
exit(EXIT_FAILURE); // failing exit status.
}
number_of_runs++;
printf("\n======= (%d) Current CC: %s ====== \n",number_of_runs, buffer);
/* construct the server_address struct
* --> memset == zeroing the server_address struct.
* --> AF_INET == IPv4 type.
* --> htons == short, network byte order converter.
* --> inet_pton == convert the IP address from String type.
*/
struct sockaddr_in server_address;
memset(&server_address, 0, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_port = htons(SERVER_PORT);
int rval = inet_pton(AF_INET, (const char*)SERVER_IP_ADDRESS, &server_address.sin_addr);
if(rval <= 0) {
printf("inet_pton() failed");
return -1;
}
/* CONNECT TO THE SERVER
* --> make a connection to the server with client_socket.
*/
int connection = connect(client_socket, (struct sockaddr *) &server_address, sizeof(server_address));
if(connection == -1) {
fprintf(stderr, "connect() failed with error code --> %s\n", strerror(errno));
exit(EXIT_FAILURE); // failing exit status.
}
else {
printf("connected to server!\n");
}
/* SEND DATA TO SERVER */
int numbytes = recv(client_socket, buffer, BUFF_SIZE, 0);
if (numbytes == -1) {
perror("recv");
exit(1);
}
buffer[numbytes] = '\0';
printf("Received from server: '%s' \n", buffer);
file_pointer = fopen(FILENAME, "r");
if(file_pointer == NULL) {
fprintf(stderr, "Failed to open file 1mb.txt : %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
int data_stream;
int size = 0;
while( ( data_stream = fread(buffer,1,sizeof(buffer),file_pointer) ) > 0 ) {
size += send(client_socket, buffer, data_stream, 0);
}
if(size == FULL_SIZE) {
printf("successfully sent 1MB file: %d\n",size);
}else {
printf("sadly sent just %d out of %d\n",size,FULL_SIZE);
}
sleep(1);
close(client_socket);
j++;
}
i++;
}
return 0;
}
Measure.c
#include <stdio.h>
#include <unistd.h> // for sleep()
#include <sys/types.h>
#include <sys/socket.h>
#include <time.h> // for clock_t, clock()
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#define SERVER_PORT 5060 //The port that the server listens.
#define BILLION 1000000000.0
#define BUFF_SIZE 1500
int main() {
// on linux to prevent crash on closing socket.
signal(SIGPIPE, SIG_IGN);
char buffer[BUFF_SIZE];
// create a socket lisener.
int socket_listener = -1;
if((socket_listener = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("Couldn't create a socket listener : %d",errno);
}
// setsockopt = used to control the options of this socket.
// Reuse the port if the server socket on was closed
// and remains for 45 seconds in TIME-WAIT state till the final removal.
int enable_reuse = 1;
if(setsockopt(socket_listener, SOL_SOCKET, SO_REUSEADDR,&enable_reuse, sizeof(int)) < 0) {
printf("setsockopt() failed with error code: %d", errno);
}
struct sockaddr_in server_address;
memset(&server_address, 0, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = INADDR_ANY;
server_address.sin_port = htons(SERVER_PORT); //network order
// connect the server to a port which can read and write on.
if(bind(socket_listener, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
printf("Bind failed with error code : %d" , errno);
return -1;
}
printf("Bind() success!\n\n");
// Make the socket listening; actually mother of all client sockets.
// 500 is a Maximum size of queue connection requests
// number of concurrent connections
if(listen(socket_listener, 500) == -1) {
printf("listen() failed with error code : %d",errno);
return -1;
}
//Accept and incoming connection
printf("Waiting for incoming TCP-connections...\n");
struct sockaddr_in client_address;
socklen_t client_address_length = sizeof(client_address);
int i = 0;
while(i < 2){
int j = 0;
double sum_for_average = 0.0;
while(j < 5) {
memset(&client_address, 0, sizeof(client_address));
// updates the length in each iteration.
client_address_length = sizeof(client_address);
// accept() gets the connection and return the socket of this connection.
int client_socket = accept(socket_listener, (struct sockaddr *)&client_address, &client_address_length);
if(client_socket == -1) {
printf("listen failed with error code : %d",errno);
close(socket_listener);
return -1;
} else {
printf("A new client connection accepted\n");
}
//Reply to client
char message[] = "welcome";
int messageLen = strlen(message) + 1;
// this method returns the size of bytes that succesfully sent.
int bytes_sent = send(client_socket, message, messageLen, 0);
if(bytes_sent == -1) {
printf("send() failed with error code : %d", errno);
}
else if(bytes_sent == 0) {
printf("peer has closed the TCP connection prior to send().\n");
}
else if (messageLen > bytes_sent) {
printf("sent only %d bytes from the required %d.\n", messageLen, bytes_sent);
}
else {
printf("'welcome' successfully sent.\n");
}
struct timespec start, end;
clock_gettime(CLOCK_REALTIME, &start);
int bytes = -1;
while(bytes != 0) {
bytes = recv(client_socket,buffer, BUFF_SIZE,0);
}
clock_gettime(CLOCK_REALTIME, &end);
// time_spent = end - start
double time_spent = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / BILLION;
printf("Time elpased is %f seconds\n\n", time_spent);
sum_for_average += time_spent;
sleep(1);
j++;
}
char cc_type[20];
if(i == 0) {
strcpy(cc_type,"cubic");
} else {
strcpy(cc_type,"reno");
}
printf("\nAverage time for CC %s is %f .\n\n",cc_type,sum_for_average/5);
i++;
}
close(socket_listener);
return 0;
}
Leave a comment