Skip to content

Instantly share code, notes, and snippets.

@jirihnidek
Last active October 16, 2023 09:33
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save jirihnidek/95c369996a81be1b854e to your computer and use it in GitHub Desktop.
Save jirihnidek/95c369996a81be1b854e to your computer and use it in GitHub Desktop.
Example of (Linux) client-server aplication using ECN bit in UDP packets.
.cproject
.project
build

Example of Client-Server Application using ECN

ECN means Explicit Congestion Notificiation an it is used for notification of congestion at network equipment between sender and receiver.

Compile

It is possible to use this code only at Linux. You will need gcc (g++), make and cmake. First of all, create directory, where source will be build:

mkdir build

Go to this directory:

cd build

Generate makefiles using cmake:

cmake ../

And build binaries using make:

 make

Testing

To run server type:

./server

To run client type:

./client localhost
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <iostream>
#include "lib.hh"
int main(int argc, char *argv[])
{
int sock_fd = -1, ret;
struct addrinfo hints, *result, *rp;
struct sockaddr_in server_sock_addr4;
struct msghdr rcv_msg;
struct msghdr snd_msg;
struct iovec rcv_iov[1];
struct iovec snd_iov[1];
char rcv_buf[MAX_BUF_SIZE];
char snd_buf[MAX_BUF_SIZE];
char str_addr[INET_ADDRSTRLEN];
char rcv_ctrl_data[MAX_CTRL_SIZE];
socklen_t optlen;
int buf_size;
/* Client program has to be run with server name as first argument */
if(argc != 2) {
std::cout << "Syntax " << argv[0] << " server" << std::endl;
return EXIT_FAILURE;
}
/* Initialize addrinfo structure ... */
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET; /* Allow IPv4 */
hints.ai_socktype = SOCK_DGRAM; /* Allow datagram protocol */
hints.ai_flags = 0; /* No flags required */
hints.ai_protocol = IPPROTO_UDP; /* Allow UDP protocol only */
std::cout << "DNS lookup for " << argv[1] << " ..." << std::endl;
/* To get IP of server */
ret = getaddrinfo(argv[1], "9001", &hints, &result);
if( ret != 0 ) {
perror("getaddrinfo()");
return EXIT_FAILURE;
}
/* Try to use addrinfo from getaddrinfo() */
for(rp=result; rp!=NULL; rp=rp->ai_next) {
sock_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if( sock_fd == -1) {
perror("socket()");
continue;
} else {
std::cout << "Socket created ..." << std::endl;
break;
}
}
memset(&server_sock_addr4, 0, sizeof(struct sockaddr_in));
server_sock_addr4.sin_family = AF_INET;
server_sock_addr4.sin_addr.s_addr = ((struct sockaddr_in*)rp->ai_addr)->sin_addr.s_addr;
server_sock_addr4.sin_port = htons(SERVER_PORT);
inet_ntop(AF_INET, &(server_sock_addr4.sin_addr), str_addr, sizeof(str_addr));
std::cout << "'Connecting' " << str_addr << " ..." << std::endl;
/* Try to "connect" to this address ... the client will be able to send and
* receive packets only from this address. */
if(connect(sock_fd, (struct sockaddr *)&server_sock_addr4, sizeof(server_sock_addr4)) == -1) {
perror("connect()");
close(sock_fd);
return EXIT_FAILURE;
}
/* Receive ECN in message */
unsigned char set = 1;
ret = setsockopt(sock_fd, IPPROTO_IP, IP_RECVTOS, &set, sizeof(set));
if(ret == -1) {
perror("setsockopt()");
close(sock_fd);
return EXIT_FAILURE;
}
/* Send ECN capable packets */
unsigned int ecn = INET_ECN_ECT_0;
ret = setsockopt(sock_fd, IPPROTO_IP, IP_TOS, &ecn, sizeof(ecn));
if(ret == -1) {
perror("setsockopt()");
close(sock_fd);
return EXIT_FAILURE;
}
/* Prepare message for sending */
snd_iov[0].iov_base = snd_buf;
snd_buf[0] = 'a';
snd_iov[0].iov_len = 1;
snd_msg.msg_name = NULL; // Socket is connected
snd_msg.msg_namelen = 0;
snd_msg.msg_iov = snd_iov;
snd_msg.msg_iovlen = 1;
snd_msg.msg_control = 0;
snd_msg.msg_controllen = 0;
/* Prepare message for receiving */
rcv_iov[0].iov_base = rcv_buf;
rcv_iov[0].iov_len = MAX_BUF_SIZE;
rcv_msg.msg_name = NULL; // Socket is connected
rcv_msg.msg_namelen = 0;
rcv_msg.msg_iov = rcv_iov;
rcv_msg.msg_iovlen = 1;
rcv_msg.msg_control = rcv_ctrl_data;
rcv_msg.msg_controllen = MAX_CTRL_SIZE;
std::cout << "Sending message ..." << std::endl;
if( sendmsg(sock_fd, &snd_msg, 0) == -1 ) {
perror("sendmsg()");
close(sock_fd);
return EXIT_FAILURE;
}
std::cout << "Waiting for response ..." << std::endl;
buf_size = recvmsg(sock_fd, &rcv_msg, 0);
if ( buf_size == -1) {
perror("recvmsg()");
close(sock_fd);
return EXIT_FAILURE;
}
std::cout << "Response received ..." << std::endl;
display_msg(&rcv_msg, buf_size);
/* Try to get ECN bits from packet using getsockopt() */
ret = getsockopt(sock_fd, IPPROTO_IP, IP_TOS, (void*)&ecn, &optlen);
if(ret == -1) {
perror("getsockopt()");
close(sock_fd);
return EXIT_FAILURE;
}
std::cout << "ECN (getsockopt): " << (int)(ecn & 0x03) << std::endl;
close(sock_fd);
return EXIT_SUCCESS;
}
# Main CMakeFile.txt
# Minimal version of CMake
cmake_minimum_required (VERSION 2.6)
# Build type
if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message(STATUS "Setting build type to 'Debug' as none was specified.")
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE)
# Set the possible values of build type for cmake-gui
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release")
endif ()
# Define project name
project (Client-Server)
# Source code for server
set (server_src server.cc)
# Source code for client
set (client_src client.cc)
# Compiler flags
if (CMAKE_COMPILER_IS_GNUCC)
set (CMAKE_CXX_FLAGS "-D_REETRANT -Wall -Wextra -pedantic -Wno-long-long")
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb -O0")
elseif( CMAKE_BUILD_TYPE STREQUAL "Release" )
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG -O3 -fno-strict-aliasing")
endif ()
endif (CMAKE_COMPILER_IS_GNUCC)
add_library(lib STATIC lib.cc lib.hh)
# Set up verse server executable
add_executable (server ${server_src})
target_link_libraries(server lib)
# Set up verse server executable
add_executable (client ${client_src})
target_link_libraries(client lib)
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <iostream>
#include "lib.hh"
void display_msg(struct msghdr *msg, int msg_len)
{
struct cmsghdr *cmptr;
char str_addr[INET_ADDRSTRLEN];
unsigned short port;
int *ecnptr;
unsigned char received_ecn;
if(msg->msg_name != NULL) {
inet_ntop(AF_INET,
&((struct sockaddr_in*)msg->msg_name)->sin_addr,
str_addr,
sizeof(struct sockaddr_in));
port = ntohs(((struct sockaddr_in*)msg->msg_name)->sin_port);
std::cout << "UDP message received from: " <<
str_addr << ":" <<
port << std::endl;
}
std::cout << "Ctrl len: " << msg->msg_controllen << std::endl;
for (cmptr = CMSG_FIRSTHDR(msg);
cmptr != NULL;
cmptr = CMSG_NXTHDR(msg, cmptr))
{
std::cout << "length:" << cmptr->cmsg_len <<
" level: " << cmptr->cmsg_level <<
" type: " << cmptr->cmsg_type << std::endl;
if(cmptr->cmsg_level == IPPROTO_IP && cmptr->cmsg_type == IP_TOS) {
ecnptr = (int*)CMSG_DATA(cmptr);
received_ecn = *ecnptr;
std::cout << "ECN: " << (int)(received_ecn & INET_ECN_MASK) << std::endl;
}
}
std::cout << "Buffer size: " << msg_len << " ..."<< std::endl;
std::cout << "Buffer dump: ";
for(int i = 0; i < msg_len; ++i) {
std::cout << ((char*)msg->msg_iov[0].iov_base)[i];
}
std::cout << std::endl;
}
#ifndef LIB_HH_
#define LIB_HH_
#define MAX_BUF_SIZE 65535
#define MAX_CTRL_SIZE 8192
#define SERVER_PORT 9001
#define INET_ECN_NOT_ECT 0x00 /* ECN was not enabled */
#define INET_ECN_ECT_1 0x01 /* ECN capable packet */
#define INET_ECN_ECT_0 0x02 /* ECN capable packet */
#define INET_ECN_CE 0x03 /* ECN congestion */
#define INET_ECN_MASK 0x03 /* Mask of ECN bits */
void display_msg(struct msghdr *msg, int msg_len);
#endif /* LIB_HH_ */
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <iostream>
#include "lib.hh"
int main(int argc, char *argv[])
{
int sock_fd = -1, flag, ret;
struct sockaddr_in sock_addr4;
struct pollfd sock_fds[1];
struct msghdr rcv_msg;
struct msghdr snd_msg;
struct sockaddr_storage src_addr;
struct sockaddr_storage dst_addr;
struct iovec rcv_iov[1];
struct iovec snd_iov[1];
char rcv_buf[MAX_BUF_SIZE];
char snd_buf[MAX_BUF_SIZE];
char rcv_ctrl_data[MAX_CTRL_SIZE];
socklen_t optlen;
(void)argc;
(void)argv;
/* Create socket */
sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sock_fd == -1) {
perror("socket()");
return EXIT_FAILURE;
}
/* Set socket to reuse addresses */
flag = 1;
ret = setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (const void*)&flag, (socklen_t)sizeof(flag));
if(ret == -1) {
perror("setsockopt()");
close(sock_fd);
return EXIT_FAILURE;
}
/* Set up server address and port */
memset(&sock_addr4, 0, sizeof(struct sockaddr_in));
sock_addr4.sin_family = AF_INET;
/* Server accept connection from any address */
sock_addr4.sin_addr.s_addr = htonl(INADDR_ANY);
/* Listen at port 9001 */
sock_addr4.sin_port = htons(SERVER_PORT);
/* Bind address and socket */
ret = bind(sock_fd, (struct sockaddr*)&sock_addr4, sizeof(sock_addr4));
if(ret == -1) {
perror("bind()");
close(sock_fd);
return EXIT_FAILURE;
}
/* Receive ECN in message */
unsigned char set = 1;
ret = setsockopt(sock_fd, IPPROTO_IP, IP_RECVTOS, &set, sizeof(set));
if(ret == -1) {
perror("setsockopt()");
close(sock_fd);
return EXIT_FAILURE;
}
/* Send ECN capable packets */
unsigned int ecn = INET_ECN_ECT_0;
ret = setsockopt(sock_fd, IPPROTO_IP, IP_TOS, &ecn, sizeof(ecn));
if(ret == -1) {
perror("setsockopt()");
close(sock_fd);
return EXIT_FAILURE;
}
/* Add socket file descriptor to array */
sock_fds[0].fd = sock_fd;
sock_fds[0].events = POLLIN;
std::cout << "Waiting ..." << std::endl;
/* Prepare message for sending */
snd_iov[0].iov_base = snd_buf;
snd_buf[0] = 'A';
snd_iov[0].iov_len = 1;
snd_msg.msg_name = &dst_addr;
snd_msg.msg_namelen = sizeof(dst_addr);
snd_msg.msg_iov = snd_iov;
snd_msg.msg_iovlen = 1;
snd_msg.msg_control = 0;
snd_msg.msg_controllen = 0;
/* Prepare message for receiving */
rcv_iov[0].iov_base = rcv_buf;
rcv_iov[0].iov_len = MAX_BUF_SIZE;
rcv_msg.msg_name = &src_addr;
rcv_msg.msg_namelen = sizeof(src_addr);
rcv_msg.msg_iov = rcv_iov;
rcv_msg.msg_iovlen = 1;
memset(rcv_ctrl_data, 0, sizeof(rcv_ctrl_data));
rcv_msg.msg_control = rcv_ctrl_data;
rcv_msg.msg_controllen = sizeof(rcv_ctrl_data);
/* Never ending loop */
while(1) {
ret = poll(sock_fds, 1, 1000);
if(ret > 0) {
ret = recvmsg(sock_fd, &rcv_msg, 0);
if(ret == - 1) {
perror("recvmsg()");
close(sock_fd);
return EXIT_FAILURE;
}
display_msg(&rcv_msg, ret);
/* Try to get ECN bits from packet using getsockopt() */
ret = getsockopt(sock_fd, IPPROTO_IP, IP_TOS, (void*)&ecn, &optlen);
if(ret == -1) {
perror("getsockopt()");
close(sock_fd);
return EXIT_FAILURE;
}
std::cout << "ECN (getsockopt): " << (int)(ecn & INET_ECN_MASK) << std::endl;
std::cout << "Sending response ..." << std::endl;
/* Set destination address:port according source address of client */
((struct sockaddr_in*)snd_msg.msg_name)->sin_family =
((struct sockaddr_in*)rcv_msg.msg_name)->sin_family;
((struct sockaddr_in*)snd_msg.msg_name)->sin_addr =
((struct sockaddr_in*)rcv_msg.msg_name)->sin_addr;
((struct sockaddr_in*)snd_msg.msg_name)->sin_port =
((struct sockaddr_in*)rcv_msg.msg_name)->sin_port;
/* Send message */
ret = sendmsg(sock_fd, &snd_msg, 0);
if( ret == -1 ) {
perror("sendmsg()");
close(sock_fd);
return EXIT_FAILURE;
}
} else if(ret == 0) {
std::cout << "Timeout ..." << std::endl;
} else {
perror("poll()");
close(sock_fd);
return EXIT_FAILURE;
}
}
close(sock_fd);
return EXIT_SUCCESS;
}
@jirihnidek
Copy link
Author

This code works only at Linux OS, because BSD UNIX like OS and Mac OS X do not support to set up option IP_RECVTOS in set setsockopt().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment