Tips and code to easily pass the computer networks practical exam ("Reti di calcolatori") in Padua.
You can find everything that is in this readme through man
, and the various RFCs. I've done it just for a fast reference.
TOC
- Useful RFCs
- Useful links
- Something to understand before trying the past exams
- Editor for the exam
- Past exams
- Misc
- HTTP-date
- rewind
- Read a file and forward it
- Read from a socket and forward to another socket (like a proxy)
- Check if the target ip is in our network
- gethostbyname
- socket creation, binding for listening and writing (like http requests)
- RAW socket creation, binding for listening and writing (to deal with tcp/icmp/arp requests directly)
- Useful utils to print packet contents
- How to printf the various things
- Useful info
- Here you can find a summary of the most important things to know to pass the exam. Anyway, maybe is better to study more in depth every topic.
- C socket programming online book. There is more than needed, but still interesting.
An Ethernet frame (data link layer) contains an IP datagram (network layer) that can contains one of the following { tcp_segment (transport layer), icmp_packet } (for the purpose of this exam). An easy way to realize that is:
eth = (struct eth_frame *)buffer;
ip = (struct ip_datagram *)eth->payload;
tcp = (struct tcp_segment *)ip->payload;
// or
icmp = (struct icmp_packet *)ip->payload;
Data types and endianess
(depends on the architecture, but you can assume that the following is true for this exam)
unsigned char
: 1 byteunsigned short
: 2 bytesunsiged int
: 4 bytes
To transfer on the network is used Big endian. Most of the intel's cpus are little endian. To convert use this 2 functions that automatically understand if a conversion is needed:
htonl(x)
orhtons(x)
to convert x from Host to Network endianess, l if you have to convert a 4 bytes variable, s a 2 bytes one.ntohl(x)
orntohs(x)
for the opposite. (You may notice that the implementation of htonx and ntohx is the same)- if a variable is 1 byte long we don't have endianess problems (obviously)
Ethernet frame
// Frame Ethernet
struct eth_frame {
unsigned char dst[6]; // mac address
unsigned char src[6]; // mac address
unsigned short type; // 0x0800 = ip, 0x0806 = arp
char payload[1500]; //ARP or IP
};
Thanks to the type
we can understand where to forward it on the next level (2 examples are ip or arp)
IP datagram
Header length: check second half of ver_ihl
attribute. Example: if it's '5', then the header length is 4 * 5 = 20 bytes.
//todo add image
// Datagramma IP
struct ip_datagram{
unsigned char ver_ihl; // first 4 bits: version, second 4 bits: (lenght header)/8
unsigned char tos; //type of service
unsigned short totlen; // len header + payload
unsigned short id; // useful in case of fragmentation
unsigned short flags_offs;//offset/8 related to the original ip package
unsigned char ttl;
unsigned char protocol; // TCP = 6, ICMP = 1
unsigned short checksum; // only header checksum (not of payload). Must be at 0 before the calculation.
unsigned int src; // ip address
unsigned int dst; // ip address
unsigned char payload[1500];
};
TCP segment
Header (as defined here) length: 20
struct tcp_segment {
unsigned short s_port;
unsigned short d_port;
unsigned int seq; // offset in bytes from the start of the tcp segment in the stream (from initial sequance n)
unsigned int ack; // useful only if ACK flag is 1. Next seq that sender expect
unsigned char d_offs_res;// first 4 bits: (header len/8)
unsigned char flags; // check rfc
unsigned short win; // usually initially a 0 (?)
unsigned short checksum; // use tcp_pseudo to calculate it. Must be at 0 before the calculation.
unsigned short urgp;
unsigned char payload[1000];
};
To calculate the checksum of a TCP segment is useful to define an additional structure (check on the relative RFC). Size of it, without the tcp_segment part
struct tcp_pseudo{
unsigned int ip_src, ip_dst;
unsigned char zeroes;
unsigned char proto; // ip datagram protocol field (tcp = 6, ip = 1)
unsigned short entire_len; // tcp length (header + data)
unsigned char tcp_segment[20/*to set appropriatly */]; // entire tcp packet pointer
};
To calculate the size of the entire tcp segment (or of the icmp), or more in general of the ip payload:
unsigned short ip_total_len = ntohs(ip->totlen);
unsigned short ip_header_dim = (ip->ver_ihl & 0x0F) * 4;
int ip_payload_len = ip_total_len-ip_header_dim;
Checksum calculation
We can use this function both for the IP datagram and the TCP segment,
but we must take care about the len
parameter.
- todo: take care about minimum size for tcp, and odd/even corner case
unsigned short checksum( unsigned char * buffer, int len){
int i;
unsigned short *p;
unsigned int tot=0;
p = (unsigned short *) buffer;
for(i=0;i<len/2;i++){
tot = tot + htons(p[i]);
if (tot&0x10000) tot = (tot&0xFFFF)+1;
}
return (unsigned short)0xFFFF-tot;
}
The 2 cases are:
- IP:
ip->checksum=htons(checksum((unsigned char*) ip, 20));
` - TCP:
int TCP_TOTAL_LEN = 20;
struct tcp_pseudo pseudo; // size of this: 12
memcpy(pseudo.tcp_segment,tcp,TCP_TOTAL_LEN);
pseudo.zeroes = 0;
pseudo.ip_src = ip->src;
pseudo.ip_dst = ip->dst;
pseudo.proto = 6;
pseudo.entire_len = htons(TCP_TOTAL_LEN); // may vary
tcp->checksum = htons(checksum((unsigned char*)&pseudo,TCP_TOTAL_LEN+12));
Convert int IP address in string
#include <arpa/inet.h>
void print_ip(unsigned int ip){
struct in_addr ip_addr;
ip_addr.s_addr = ip;
printf("%s\n",inet_ntoa(ip_addr));
}
I advice VIM. And please, indent your code.
:wq
to save and quit.- Press
esc
2 times if you don't understand what is happening /query
to search for "query",n
andN
to search prev/next result
Put this in ~/.vimrc to save time:
" auto reformat when you pres F7
map <F7> mzgg=G`z
" F8 to save and compile creating np executable
map <F8> :w <CR> :!gcc % -o np -g <CR>
" F9 to execute
map <F9> :!./np <CR>
" make your code look nicer
set tabstop=3
set shiftwidth=3
set softtabstop=0 expandtab
set incsearch
set cindent
" Ctrl+shift+up/down to swap the line up or doen
nnoremap <C-S-Up> <Up>"add"ap<Up>
nnoremap <C-S-Down> "add"ap
" ctrl+h to hilight the last search
nnoremap <C-h> :set hlsearch!<CR>
set number
set cursorline
set mouse=a
set foldmethod=indent
set foldlevelstart=99
let mapleader="\<space>"
nnoremap <leader>b :make <CR> :cw <CR>
First of all create a makefile
in the directory with the files to compile, like this:
np : ws18.c
gcc -o np ws18.c
Pay attention to put a tab before "gcc", and not spaces (if you have expandtab enabled in vim, use ctrl=v tab
).
Here np
is what you want generate (the executable), and ws18.c
the file to compile. In the line below there is the command to call each time you write :make
in vim.
Then, with the .vimrc
provided above, press space
(release it) and b
(build).
The command will be excuted, and you will see in the bottom of your code the list of errors.
You can fastly jump in the correct line by pressing enter in each entry.
To move between the top and bottom split press CTRL+W
W
. To close the bottom view (quickfix) :q
, or :cw
.
You can find the complete exam statement in the site at the beginning of this readme. The complete code is in the folders.
Past exams
Implement TCP three way handshake (ACK+SYN).
Tips: You can check with wireshark if your TCP checksum is correct or not.
- Is the option field to include?
Implement echo reply only for icmp requests of a certain size
Tips: You can calculate the size of an icmp message in this way:
unsigned short dimension = ntohs(ip->totlen);
unsigned short header_dim = (ip->ver_ihl & 0x0F) * 4;
int icmp_dimension = dimension-header_dim;
Implement an HTTP server that:
- redirect to a default page if the target resource is not available
- Send a temporary unavailable response if the resource is available, and after a second request gives the output.
Tips:
The header Retry-After
is ignored by most web browser, so the redirect will not take place after 10 seconds, but immediately.
In the solution there is an array that keeps the state of the connection for each ip.
Implement an ICMP "Destination unreachable" that say that the port is unavailable
Tips:
you have to send the package in response to a tcp connection. icmp->type = 3
, icmp->code=3
.
And remember to copy in the payload the content of the icmp original payload.
Intercept the first received connection, and print sequence and acknowledge numbers of them. Then reconstruct the 2 streams in 2 different buffers, and print their content.
Tips: To intercept the end of the connection, just check if a package contains the FIN bit at 1 (after having filtered all the packages, maintaining only the ones belonging to the first connection). Use the tcp sequence field to copy the contnet at the right offset in the 2 buffers. DON'T DUPLICATE CODE.
Modify the proxy to allow the request only from a pool of IP addresses, and allow only the transfer of files with text or html.
Tips: Is better to first receive the response from the server in a buffer, then copy this content to another buffer to extract headers as always. This because the header extraction procedure modifies the buffer. If the condition of the Content-type is fullfilled then just forward the contnet of the initial buffer.
Send HTTP response with a chunked body.
Tips:
Add Content-Type: text/plain\r\nTransfer-Encoding: chunked\r\n
to HTTP headers.
Then, to build each chunk to send, you can use something like:
Code to build a chunk
int build_chunk(char * s, int len){
sprintf(chunk_buffer,"%x\r\n",len); // size in hex
// debug printf("%d in hex: %s",len,chunk_buffer);
int from = strlen(chunk_buffer);
int i = 0;
for (;i < len; i++)
chunk_buffer[from+i] = s[i];
chunk_buffer[from+(i++)] = '\r';
chunk_buffer[from+(i++)] = '\n';
chunk_buffer[i+from] = 0;
return i+from;
}
Implement the Last-Modified
header of HTTP/1.0
Tips:
Some useful time conversion functions in the misc section. It could also have been done without the need of these conversions.
The HTTP date format is %a, %d %b %Y %H:%M:%S %Z
1: content length (was already implemented) 2: trace (??)
- How does www.webtrace.com work?
Modify icmp echo to split the request into two IP datagrams, one with a payload size of 16 bytes and the other one with the requested payload size.
During the course some homeworks (not mandatory) are assigned. Even if they are not exams they have more or less the same level of difficulty.
Homeworks
Implement a traceroute program, tracking each node the packets visits before reching the destination. Hint: when the time to live becomes 0 a node discards the packet and send a "Time Exceeded Message" to the source IP address of the packet (see RFC793).
use the HTTP header WWW-Authenticate
to require the access credentials to the client.
implement a client that accepts chunked content.
The HTTP date format is %a, %d %b %Y %H:%M:%S %Z
Some useful functions to deal with HTTP time
char date_buf[1000];
char* getNowHttpDate(){
time_t now = time(0);
struct tm tm = *gmtime(&now);
strftime(date_buf, sizeof date_buf, "%a, %d %b %Y %H:%M:%S %Z", &tm);
printf("Time is: [%s]\n", date_buf);
return date_buf;
}
// parse time and convert it to millisecond from epoch
time_t httpTimeToEpoch(char * time){
struct tm tm;
char buf[255];
memset(&tm, 0, sizeof(struct tm));
strptime(time,"%a, %d %b %Y %H:%M:%S %Z", &tm);
return mktime(&tm);
}
// returns 1 if d1 < d2
unsigned char compareHttpDates(char * d1, char * d2){
return httpTimeToEpoch(d1) < httpTimeToEpoch(d2);
}
unsigned char expired(char * uri, char * last_modified){
char * complete_name = uriToCachedFile(uri);
FILE * fp = fopen(complete_name,"r");
if (fp == NULL) return 1;
//read the first line
char * line = 0; size_t len = 0;
getline(&line,&len,fp);
if (compareHttpDates(last_modified,line)) return 0;
return 1;
//todo read First line and compare
}
rewind(FILE*)
set the cursor at the beginning
Read a file and forward it
FILE *fin;
if ((fin = fopen(uri + 1, "rt")) == NULL) { // the t is useless
printf("File %s non aperto\n", uri + 1);
sprintf(response, "HTTP/1.1 404 File not found\r\n\r\n<html>File non trovato</html>");
t = write(s2, response, strlen(response));
if (t == -1) {
perror("write fallita");
return -1;
}
} else {
content_length = 0;
while ((c = fgetc(fin)) != EOF) content_length++; // get file lenght
sprintf(response, "HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Length: %d\r\n\r\n", content_length);
printf("Response: %s\n", response);
//send header
t = write(s2, response, strlen(response));
//rewind the file
rewind(fin);
//re-read the file, char per char
while ((c = fgetc(fin)) != EOF) {
//printf("%c", c);
//sending the file, char per char
if (write(s2, (unsigned char *)&c, 1) != 1) {
perror("Write fallita");
}
}
fclose(fin);
}
char car;
while (read(s3, &car, 1)) {
write(s2, &car, 1);
// printf("%c",car);
}
unsigned char targetip[4] = {147, 162, 2, 100};
unsigned int netmask = 0x00FFFFFF;
if ((*((unsigned int *)targetip) & netmask) == (*((unsigned int *)myip) & netmask))
nexthop = targetip;
else
nexthop = gateway;
from hostname (like www.google.it) to ip address
/**
struct hostent {
char *h_name; // official name of host
char **h_aliases; // alias list
int h_addrtype; // host address type
int h_length; // length of address
char **h_addr_list; // list of addresses
}
#define h_addr h_addr_list[0] // for backward compatibility
*/
struct hostent *he;
he = gethostbyname(hostname);
printf("Indirizzo di %s : %d.%d.%d.%d\n", hostname,
(unsigned char)(he->h_addr[0]), (unsigned char)(he->h_addr[1]),
(unsigned char)(he->h_addr[2]), (unsigned char)(he->h_addr[3]));
For listening:
Socket creation and options setting
int s = socket(AF_INET, // domain: ipv4
/*
SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data
transmission mechanism may be supported.
SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length).
SOCK_RAW Provides raw network protocol access.
*/
SOCK_STREAM, // type: stream
0); // protocol (0=ip), check /etc/protocols
if (s == -1) {
perror("Socket Fallita");
return 1;
}
// https://stackoverflow.com/questions/3229860/what-is-the-meaning-of-so-reuseaddr-setsockopt-option-linux
// SO_REUSEADDR allows your server to bind to an address which is in a TIME_WAIT state.
int yes = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
perror("setsockopt");
return 1;
}
Binding to a local port
struct sockaddr_in indirizzo;
indirizzo.sin_family = AF_INET;
indirizzo.sin_port = htons(8987);
indirizzo.sin_addr.s_addr = 0;
t = bind(s, (struct sockaddr *)&indirizzo, sizeof(struct sockaddr_in));
if (t == -1) {
perror("Bind fallita");
return 1;
}
t = listen(s,
// backlog defines the maximum length for the queue of pending connections.
10);
if (t == -1) {
perror("Listen Fallita");
return 1;
}
Accepting a connection, and reading the actual content of the buffer
int lunghezza = sizeof(struct sockaddr_in);
// the remote address will be placed in indirizzo_remoto
s2 = accept(s, (struct sockaddr *)&indirizzo_remoto, &lunghezza);
if (s2 == -1) {
perror("Accept Fallita");
return 1;
}
// now we can read in this way:
char buffer[10000];
int i;
for (i = 0; (t = read(s2, buffer+i, 1)) > 0; i++); // ps. it's not a good way
// if the previous read returned -1
if (t == -1) {
perror("Read Fallita");
return 1;
}
At the end, remember to close all the sockets with close(s)
(where s in the socket you want to close)
RAW socket creation, binding for listening and writing (to deal with tcp/icmp/arp requests directly)
int s = socket(
//AF_PACKET Low level packet interface packet(7)
AF_PACKET,
//SOCK_RAW Provides raw network protocol access.
SOCK_RAW,
// When protocol is set to htons(ETH_P_ALL), then all protocols are received.
htons(ETH_P_ALL));
unsigned char buffer[1500];
bzero(&sll, sizeof(struct sockaddr_ll));
struct sockaddr_ll sll;
sll.sll_ifindex = if_nametoindex("eth0");
len = sizeof(sll);
int t = sendto(s, //socket
buffer, //things to send
14 + 20 + 28, // len datagram
0, //flags
(struct sockaddr *)&sll, // destination addr
len // dest addr len
);
// to receive
t = recvfrom(s, buffer, 1500, 0, (struct sockaddr *)&sll, &len);
if (t == -1) {
perror("recvfrom fallita");
return 1;
}
Ethernet packet
void stampa_eth( struct eth_frame* e ){
printf( "\n\n ***** PACCHETTO Ethernet *****\n" );
printf( "Mac destinazione: %x:%x:%x:%x:%x:%x\n", e->dst[0], e->dst[1], e->dst[2], e->dst[3], e->dst[4], e->dst[5] );
printf( "Mac sorgente: %x:%x:%x:%x:%x:%x\n", e->src[0], e->src[1], e->src[2], e->src[3], e->src[4], e->src[5] );
printf( "EtherType: 0x%x\n", htons( e->type ) );
}
IP datagram
void stampa_ip( struct ip_datagram* i ){
unsigned int ihl = ( i->ver_ihl & 0x0F) * 4; // Lunghezza header IP
unsigned int totlen = htons( i->totlen ); // Lunghezza totale pacchetto
unsigned int opt_len = ihl-20; // Lunghezza campo opzioni
printf( "\n\n ***** PACCHETTO IP *****\n" );
printf( "Version: %d\n", i->ver_ihl & 0xF0 );
printf( "IHL (bytes 60max): %d\n", ihl );
printf( "TOS: %d\n", i->tos );
printf( "Lunghezza totale: %d\n", totlen );
printf( "ID: %x\n", htons( i->id ) );
unsigned char flags = (unsigned char)( htons( i->flag_offs) >> 13);
printf( "Flags: %d | %d | %d \n", flags & 4, flags & 2, flags & 1 );
printf( "Fragment Offset: %d\n", htons( i->flag_offs) & 0x1FFF );
printf( "TTL: %d\n", i->ttl );
printf( "Protocol: %d\n", i->proto );
printf( "Checksum: %x\n", htons( i->checksum ) );
unsigned char* saddr = ( unsigned char* )&i->saddr;
unsigned char* daddr = ( unsigned char* )&i->daddr;
printf( "IP Source: %d.%d.%d.%d\n", saddr[0], saddr[1], saddr[2], saddr[3] );
printf( "IP Destination: %d.%d.%d.%d\n", daddr[0], daddr[1], daddr[2], daddr[3] );
if( ihl > 20 ){
// Stampa opzioni
printf( "Options: " );
for(int j=0; j < opt_len ; j++ ){
printf("%.3d(%.2x) ", i->payload[j], i->payload[j]);
}
printf( "\n" );
}
}
ARP
void stampa_arp( struct arp_packet* a ){
printf( "\n\n ***** PACCHETTO ARP *****\n" );
printf( "Hardware type: %d\n", htons( a->htype ) );
printf( "Protocol type: %x\n", htons( a->ptype ) );
printf( "Hardware Addr len: %d\n", a->hlen );
printf( "Protocol Addr len: %d\n", a->plen );
printf( "Operation: %d\n", htons( a->op ) );
printf( "HW Addr sorgente: %x:%x:%x:%x:%x:%x\n", a->hsrc[0], a->hsrc[1], a->hsrc[2], a->hsrc[3], a->hsrc[4], a->hsrc[5] );
printf( "IP Source: %d.%d.%d.%d\n", a->psrc[0], a->psrc[1], a->psrc[2], a->psrc[3] );
printf( "HW Addr Destinazione: %x:%x:%x:%x:%x:%x\n", a->hdst[0], a->hdst[1], a->hdst[2], a->hdst[3], a->hdst[4], a->hdst[5] );
printf( "IP Dest: %d.%d.%d.%d\n", a->pdst[0], a->pdst[1], a->pdst[2], a->pdst[3] );
}
ICMP content
void stampa_icmp( struct icmp_packet* i ){
printf( "\n\n ***** PACCHETTO ICMP *****\n" );
printf( "Type: %d\n", i->type );
printf( "Code: %d\n", i->code );
printf( "Code: 0x%x\n", htons( i->checksum ) );
printf( "ID: %d\n", htons(i->id) );
printf( "Sequence: %d\n", htons(i->seq) );
}
TCP
void stampa_tcp( struct tcp_segment* t ){
printf( "\n\n ***** PACCHETTO TCP *****\n" );
printf( "Source Port: %d\n", htons( t->s_port ) );
printf( "Source Port: %d\n", htons( t->d_port ) );
printf( "Sequence N: %d\n", ntohl( t->seq ) );
printf( "ACK: %d\n", ntohl( t->ack ) );
printf( "Data offset (bytes): %d\n", ( t->d_offs_res >> 4 ) * 4 );
printf( "Flags: " );
printf( "CWR=%d | ", (t->flags & 0x80) >> 7 );
printf( "ECE=%d | ", (t->flags & 0x40) >> 6 );
printf( "URG=%d | ", (t->flags & 0x20) >> 5 );
printf( "ACK=%d | ", (t->flags & 0x10) >> 4 );
printf( "PSH=%d | ", (t->flags & 0x08) >> 3 );
printf( "RST=%d | ", (t->flags & 0x04) >> 2 );
printf( "SYN=%d | ", (t->flags & 0x02) >> 1 );
printf( "FIN=%d\n", (t->flags & 0x01) );
printf( "Windows size: %d\n", htons( t->win ) );
printf( "Checksum: 0x%x\n", htons( t->checksum ) );
printf( "Urgent pointer: %d\n", htons( t->urgp ) );
}
Not really useful, but..
// es. tcp.c
printf("%.4d. // delta_sec (unsigned int)
%.6d // delta_usec
%.5d->%.5d // ports (unsigned short)
%.2x // tcp flags (unsigned char) in hex: es: "12"
%.10u // seq (unsigned int)
%.10u // ack
%.5u //tcp win
%4.2f\n", delta_sec, delta_usec, htons(tcp->s_port), htons(tcp->d_port), tcp->flags, htonl(tcp->seq) - seqzero, htonl(tcp->ack) - ackzero, htons(tcp->win), (htonl(tcp->ack) - ackzero) / (double)(delta_sec * 1000000 + delta_usec));
/etc/services
: To know all the TCP ports available at the application level./etc/protocols
nslookup <URL>
: finds the ip address of the specified URL (example: www.google.com)netstat -rn
shows routing tabletraceroute
routes an ip packet in which path it travels by printing the IP of every gateway that decides to drop the packet that was forged with low TTL (time to live, decremented on every hop) count.