You are on page 1of 50

Unit 4 Advanced Sockets

IPv4 and IPv6 interoperability – threaded servers – thread creation and termination – TCP
echo server using threads – Mutexes – condition variables – raw sockets – raw socket
creation – raw socket input – raw socket output – ping program – trace route program.
1. IPv4 and IPv6 interoperability
IPv4 Client, IPv6 Server
A general property of a dual-stack host is that IPv6 servers can handle both IPv4 and
IPv6 clients.
Figure: IPv6 server on dual-stack host serving IPv4 and IPv6 clients.

We have an IPv4 client and an IPv6 client on the left. The server on the right is written
using IPv6 and it is running on a dual-stack host. The server has created an IPv6 listening TCP
socket that is bound to the IPv6 wildcard address and TCP port 9999.
We assume the clients and server are on the same Ethernet. We assume both clients send
SYN segments to establish a connection with the server. The IPv4 client host will send the SYN
in an IPv4 datagram and the IPv6 client host will send the SYN in an IPv6 datagram. The TCP
segment from the IPv4 client appears on the wire as an Ethernet header followed by an IPv4
header, a TCP header, and the TCP data. The Ethernet header contains a type field of 0x0800,
which identifies the frame as an IPv4 frame. The TCP header contains the destination port of
9999. The destination IP address in the IPv4 header, which we do not show, would be
206.62.226.42.
The TCP segment from the IPv6 client appears on the wire as an Ethernet header
followed by an IPv6 header, a TCP header, and the TCP data. The Ethernet header contains a
type field of 0x86dd, which identifies the frame as an IPv6 frame. The TCP header has the same
format as the TCP header in the IPv4 packet and contains the destination port of 9999. The
destination IP address in the IPv6 header, which we do not show, would be
5f1b:df00:ce3e:e200:20:800:2b37:6426.
The receiving datalink looks at the Ethernet type field and passes each frame to the
appropriate IP module. The IPv4 module, probably in conjunction with the TCP module, detects
that the destination socket is an IPv6 socket, and the source IPv4 address in the IPv4 header is
converted into the equivalent IPv4-mapped IPv6 address. That mapped address is returned to the
IPv6 socket as the client's IPv6 address when accept returns to the server with the IPv4 client
connection. All remaining datagrams for this connection are IPv4 datagrams.
When accept returns to the server with the IPv6 client connection, the client's IPv6
address does not change from whatever source address appears in the IPv6 header. All remaining
datagrams for this connection are IPv6 datagrams.
We can summarize the steps that allow an IPv4 TCP client to communicate with an IPv6
server as follows:
1. The IPv6 server starts, creates an IPv6 listening socket, and we assume it binds the
wildcard address to the socket.
2. The IPv4 client calls gethostbyname and finds an A record for the server. The server host
will have both an A record and a AAAA record since it supports both protocols, but the
IPv4 client asks for only an A record.
3. The client calls connect and the client's host sends an IPv4 SYN to the server.
4. The server host receives the IPv4 SYN directed to the IPv6 listening socket, sets a flag
indicating that this connection is using IPv4-mapped IPv6 addresses, and responds with
an IPv4 SYN/ACK. When the connection is established, the address returned to the
server by accept is the IPv4-mapped IPv6 address.
5. When the server host sends to the IPv4-mapped IPv6 address, its IP stack generates IPv4
datagrams to the IPv4 address. Therefore, all communication between this client and
server takes place using IPv4 datagrams.
6. Unless the server explicitly checks whether this IPv6 address is an IPv4-mapped IPv6
address.
An underlying assumption in this scenario is that the dual-stack server host has both an
IPv4 address and an IPv6 address. This will work until all the IPv4 addresses are taken. The
scenario is similar for an IPv6 UDP server, but the address format can change for each datagram.
The below figure summarizes how a received IPv4 or IPv6 datagram is processed,
depending on the type of the receiving socket, for TCP and UDP, assuming a dual-stack host.
Fig: Processing of received IPv4 or IPv6 datagrams, depending on type of receiving socket.
If an IPv4 datagram is received for an IPv4 socket. These are the two arrows labeled "IPv4"
in the figure: one to TCP and one to UDP. IPv4 datagrams are exchanged between the
client and server.
If an IPv6 datagram is received for an IPv6 socket. These are the two arrows labeled "IPv6"
in the figure: one to TCP and one to UDP. IPv6 datagrams are exchanged between the
client and server.
When an IPv4 datagram is received for an IPv6 socket, the kernel returns the corresponding
IPv4-mapped IPv6 address as the address returned by accept (TCP) or recvfrom (UDP).
These are the two dashed arrows in the figure. This mapping is possible because an IPv4
address can always be represented as an IPv6 address. IPv4 datagrams are exchanged
between the client and server.
The converse of the previous bullet is false: In general, an IPv6 address cannot be
represented as an IPv4 address; therefore, there are no arrows from the IPv6 protocol box
to the two IPv4 sockets
Most dual-stack hosts should use the following rules in dealing with listening sockets:
1. A listening IPv4 socket can accept incoming connections from only IPv4 clients.
2. If a server has a listening IPv6 socket that has bound the wildcard address and the
IPV6_V6ONLY socket option is not set, that socket can accept incoming connections
from either IPv4 clients or IPv6 clients.
3. If a server has a listening IPv6 socket that has bound an IPv6 address other than an IPv4-
mapped IPv6 address, or has bound the wildcard address but has set the IPv6_V6ONLY
socket option, that socket can accept incoming connections from IPv6 clients only.
IPv6 Client, IPv4 Server
We now swap the protocols used by the client and server from the example in the
previous section. First consider an IPv6 TCP client running on a dual-stack host.
1. An IPv4 server starts on an IPv4-only host and creates an IPv4 listening socket.
2. The IPv6 client starts and calls getaddrinfo asking for only IPv6 addresses. Since the
IPv4-only server host has only A records, that an IPV4-mapped IPv6 address is returned
to the client.
3. The IPv6 client calls connect with the IPv4-mapped IPv6 address in the IPv6 socket
address structure. The kernel detects the mapped address and automatically sends an IPv4
SYN to the server.
4. The server responds with an IPv4 SYN/ACK, and the connection is established using
IPv4 datagrams.
Figure: Processing of client requests, depending on address type and socket type.

We can summarize this scenario in the above figure.


If an IPv4 TCP client calls connect specifying an IPv4 address, or if an IPv4 UDP client
calls sendto specifying an IPv4 address, nothing special is done. These are the two arrows
labeled "IPv4" in the figure.
If an IPv6 TCP client calls connect specifying an IPv6 address, or if an IPv6 UDP client
calls sendto specifying an IPv6 address, nothing special is done. These are the two arrows
labeled "IPv6" in the figure.
If an IPv6 TCP client specifies an IPv4-mapped IPv6 address to connect or if an IPv6 UDP
client specifies an IPv4-mapped IPv6 address to sendto, the kernel detects the mapped
address and causes an IPv4 datagram to be sent instead of an IPv6 datagram. These are the
two dashed arrows in the figure.
An IPv4 client cannot specify an IPv6 address to either connect or sendto because a 16-byte
IPv6 address does not fit in the 4-byte in_addr structure within the IPv4 sockaddr_in
structure. Therefore, there are no arrows from the IPv4 sockets to the IPv6 protocol box in
the figure.
IPv6_ADDRFORM Socket Option
The IPv6_ADDRFORM socket option can change a socket from one type to another,
subject to the following restrictions:
i. An IPv4 socket can always be changed to an IPv6 socket. Any IPv4 addresses already
associated with the socket are converted to IPv4 mapped IPv6 addresses.
ii. An IPv6 socket can be changed to an IPv4 addresses already associated with the socket are
IPv4 mapped IPv6 addresses.
Converting an IPv4 to IPv6 socket
int af;
socklen_t clilen;
struct sockaddr_in6 cli;
struct hostent *ptr;
af = AF_INET6;
setsockopt(STDIN_FILENO, IPPROTO_IPv6, IPv6_ADDRFORM, &af, sizeof(af));
clilen = sizeof(clilen);
getpeername(0, &cli, &clilen);
ptr = gethostbyaddr(&cli.sin6_addr,16,AF_INET6);
2. Threaded servers
A thread can be defined as a path of execution. For every application there is one main
thread or primary thread. It may or may not have secondary threads. If the application supports
more than one thread then it is called multithreaded application.
Every thread belongs to its parent process. A process is nothing but a running program which
has its own address space and its own stack.

In the traditional Unix model, when a process needs something performed by another entity,
it forks a child process and lets the child perform the processing. In concurrent server the parent
accepts the connection, forks a child, and the child handles the client. There are problems with
fork:
fork is expensive. Memory is copied from the parent to the child, all descriptors are
duplicated in the child, and so on. Current implementations use a technique called copy-on-
write, which avoids a copy of the parent's data space to the child until the child needs its
own copy.
IPC is required to pass information between the parent and child after the fork. Passing
information from the parent to the child before the fork is easy, since the child starts with a
copy of the parent's data space and with a copy of all the parent's descriptors. But, returning
information from the child to the parent takes more work.
Threads help with both problems. Threads are sometimes called lightweight processes since a
thread is "lighter weight" than a process. That is, thread creation can be 10–100 times faster than
process creation.
All threads within a process share the same global memory. This makes the sharing of
information easy between the threads, but along with this simplicity comes the problem of
synchronization.
More than just the global variables are shared. All threads within a process share the following:
Process instructions
Most data
Open files (e.g., descriptors)
Signal handlers and signal dispositions
Current working directory
User and group IDs
But each thread has its own
Thread ID
Set of registers, including program counter and stack pointer
Stack (for local variables and return addresses)
errno
Signal mask
Priority

Difference between Process and Thread


Sl. No Process Thread
1. Processes are nothing but the set of Thread is nothing but the path of execution.
instructions that should be executed For every single task a single thread is created.
in some specific manner.
2. Every process has its own address Threads executed within the address space of
space. their parent process.
3. A single process may contain A thread is sometimes called as lightweight
multiple threads. process.
4. Process may be independent. Threads are dependent on their parent process.

3. Thread creation and termination


pthread_create Function
When a program is started by exec, a single thread is created, called the initial thread or
main thread. Additional threads are created by pthread_create.
#include <pthread.h>
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);
Returns: 0 if OK, positive Exxx value on error
Each thread within a process is identified by a thread ID, whose datatype is pthread_t .
On successful creation of a new thread, its ID is returned through the pointer tid.
Each thread has several attributes: its priority, its initial stack size, whether it should be a
daemon thread or not, and so on.
When a thread is created, we can specify these attributes by initializing a pthread_attr_t
variable that overrides the default.
We normally take the default, in which case, we specify the attr argument as a null pointer.
Finally, when we create a thread, we specify a function for it to execute. The thread starts by
calling this function and then terminates either explicitly (by calling pthread_exit) or
implicitly (by letting the function return).
The address of the function is specified as the func argument, and this function is called with
a single pointer argument, arg.
The return value from the Pthread functions is normally 0 if successful or nonzero on an
error.
The Pthread functions return the positive error indication as the function's return value.
For example, if pthread_create cannot create a new thread because of exceeding some system
limit on the number of threads, the function return value is EAGAIN.
The Pthread functions do not set errno. The convention of 0 for success or nonzero for an
error is fine since all the Exxx values in <sys/errno.h> are positive. A value of 0 is never
assigned to one of the Exxx names.
pthread_join Function
We can wait for a given thread to terminate by calling pthread_join. Comparing threads
to Unix processes, pthread_create is similar to fork, and pthread_join is similar to waitpid.
#include <pthread.h>
int pthread_join (pthread_t tid, void ** status);
Returns: 0 if OK, positive Exxx value on error
We must specify the tid of the thread that we want to wait for. Unfortunately, there is no
way to wait for any of our threads. If the status pointer is non-null, the return value from the
thread is stored in the location pointed to by status.
pthread_self Function
Each thread has an ID that identifies it within a given process. The thread ID is returned
by pthread_create and we saw it was used by pthread_join. A thread fetches this value for itself
using pthread_self.
#include <pthread.h>
pthread_t pthread_self (void);
Returns: thread ID of calling thread
Comparing threads to Unix processes, pthread_self is similar to getpid.
pthread_detach Function
A thread is either joinable (the default) or detached (disconnected). When a joinable
thread terminates, its thread ID and exit status are maintained until another thread calls
pthread_join. But a detached thread is like a daemon process: When it terminates, all its
resources are released and we cannot wait for it to terminate. If one thread needs to know when
another thread terminates, it is best to leave the thread as joinable. The pthread_detach function
changes the specified thread so that it is detached.
#include <pthread.h>
int pthread_detach (pthread_t tid);
Returns: 0 if OK, positive Exxx value on error
This function is commonly called by the thread that wants to detach itself, as in
pthread_detach (pthread_self());
pthread_exit Function
One way for a thread to terminate is to call pthread_exit.
#include <pthread.h>
void pthread_exit (void *status);
If the thread is not detached, its thread ID and exit status are maintained for a later
pthread_join by some other thread in the calling process. The pointer status must not point to an
object that is local to the calling thread since that object disappears when the thread terminates.
There are two other ways for a thread to terminate:
The function that started the thread (the third argument to pthread_create) can return. Since
this function must be declared as returning a void pointer that return value is the exit status
of the thread.
If the main function of the process returns or if any thread calls exit, the process terminates,
including any threads.
4. TCP echo server using threads
We write TCP echo server using one thread per client instead of one child process per client.
We also make it protocol-independent, using our tcp_listen function.
Create thread
17–21 When accept returns, we call pthread_create instead of fork. The first argument
is a null pointer, since we do not care about the thread ID. The single argument that we pass to
the doit function is the connected socket descriptor, connfd. We cast the integer descriptor
connfd to be a void pointer.
Thread function
23–30 doit is the function executed by the thread. The thread detaches itself since there is
no reason for the main thread to wait for each thread it creates. When str_echo function returns,
we must close the connected socket since the thread shares all descriptors with the main thread.
With fork, the child did not need to close the connected socket because when the child
terminated, all open descriptors were closed on process termination.
Also notice that the main thread does not close the connected socket, which we always
did with a concurrent server that calls fork. This is because all threads within a process share the
descriptors, so if the main thread called close, it would terminate the connection. Creating a new
thread does not affect the reference counts for open descriptors, which is different from fork.
Program for TCP echo server using threads
1 #include "unpthread.h"
2 static void *doit(void *); /* each thread executes this function */
3 int
4 main(int argc, char **argv)
5{
6 int listenfd, connfd;
7 pthread_t tid;
8 socklen_t addrlen, len;
9 struct sockaddr *cliaddr;
10 if (argc == 2)
11 listenfd = tcp_listen(NULL, argv[1], &addrlen);
12 else if (argc == 3)
13 listenfd = tcp_listen(argv[1], argv[2], &addrlen);
14 else
15 err_quit("usage: tcpserv01 [ <host> ] <service or port>");
16 cliaddr = malloc(addrlen);
17 for (; ; ) {
18 len = addrlen;
19 connfd = accept(listenfd, cliaddr, &len);
20 pthread_create(&tid, NULL, &doit, (void *) connfd);
21 }
22 }
23 static void *
24 doit(void *arg)
25 {
26 pthread_detach(pthread_self());
27 str_echo((int) arg); /* same function as before */
28 close((int) arg); /* done with connected socket */
29 return (NULL);
30 }
Passing Arguments to New Threads
In the above program we cast the integer variable connfd to be a void pointer, but this is
not guaranteed to work on all systems. To handle this correctly requires additional work.
First, notice that we cannot just pass the address of connfd to the new thread. That is, the
following does not work:
int main(int argc, char **argv)
{
int listenfd, connfd;
...

for ( ; ; )
{
len = addrlen;
connfd = accept(listenfd, cliaddr, &len);
pthread_create(&tid, NULL, &doit, &connfd);
}
}
static void * doit(void *arg)
{
int connfd;
connfd = * ((int *) arg);
pthread_detach (pthread_self());
str_echo (connfd); /* same function as before */
close (connfd); /* done with connected socket */
return (NULL);
}
There is one integer variable, connfd in the main thread, and each call to accept
overwrites this variable with a new value (the connected descriptor). The following scenario can
occur:
accept returns, connfd is stored into (say the new descriptor is 5), and the main thread calls
pthread_create. The pointer to connfd (not its contents) is the final argument to
pthread_create.
A thread is created and the doit function is scheduled to start executing.
Another connection is ready and the main thread runs again (before the newly created
thread). accept returns, connfd is stored into (say the new descriptor is now 6), and the
main thread calls pthread_create.
Even though two threads are created, both will operate on the final value stored in
connfd, which we assume is 6. The problem is that multiple threads are accessing a shared
variable (the integer value in connfd) with no synchronization. The below program shows the
better olution to this problem.
17–22 Each time we call accept, we first call malloc and allocate space for an integer variable,
the connected descriptor. This gives each thread its own copy of the connected descriptor.
28–29 The thread fetches the value of the connected descriptor and then calls free to release the
memory.
Program for TCP echo server using threads with more portable argument passing.
1 #include "unpthread.h"
2 static void *doit(void *); /* each thread executes this function */
3 int
4 main(int argc, char **argv)
5{
6 int listenfd, *iptr;
7 thread_t tid;
8 socklen_t addrlen, len;
9 struct sockaddr *cliaddr;
10 if (argc == 2)
11 listenfd = Tcp_listen(NULL, argv[1], &addrlen);
12 else if (argc == 3)
13 listenfd = Tcp_listen(argv[1], argv[2], &addrlen);
14 else
15 err_quit("usage: tcpserv01 [ <host> ] <service or port>");
16 cliaddr = malloc(addrlen);
17 for ( ; ; ) {
18 len = addrlen;
19 iptr = malloc(sizeof(int));
20 *iptr = accept(listenfd, cliaddr, &len);
21 pthread_create(&tid, NULL, &doit, iptr);
22 }
23 }
24 static void *
25 doit(void *arg)
26 {
27 int connfd;
28 connfd = *((int *) arg);
29 free(arg);
30 pthread_detach(pthread_self());
31 str_echo(confd); /* same function as before */
32 close(confd); /* done with connected socket */
33 return (NULL);
34 }
5. Mutexes
A mutex is a thread synchronization object, it can be used by threads to control access to
a shared resource. A mutex can be locked to indicate a resource is in use, and other threads can
then block on the mutex to wait for the resource. “Mutex” is short for “mutual exclusion”.
The function that each thread executes with variables, that are global not thread-specific.
If one thread is in the middle of decrementing a variable, that thread is suspended, and if another
thread executes and decrements the same variable, an error can result. For example, assume that
the C compiler turns the decrement operator into three instructions: load from memory into a
register, decrement the register, and store from the register into memory. Consider the following
possible scenario:
1. Thread A is running and it loads the value of nconn (3) into a register.
2. The system switches threads from A to B. A's registers are saved, and B's registers are
restored.
3. Thread B executes the three instructions corresponding to the C expression nconn--,
storing the new value of 2.
4. Sometime later, the system switches threads from B to A. A's registers are restored and A
continues where it left off, at the second machine instruction in the three-instruction
sequence. The value of the register is decremented from 3 to 2, and the value of 2 is
stored in nconn.
The end result is that nconn is 2 when it should be 1. This is wrong.
We call threads programming concurrent programming, or parallel programming, since
multiple threads can be running concurrently (in parallel), accessing the same variables. While
the error scenario occurs in a single-CPU system, the possible for error also exists if threads A
and B are running at the same time on different CPUs on a multiprocessor system. With normal
Unix programming, we do not encounter these concurrent programming problems because with
fork, nothing besides descriptors is shared between the parent and child.
We can easily demonstrate this problem with threads. The following program is a simple
program that creates two threads and then has each thread increment a global variable 5,000
times.
Program for Two threads that increment a global variable incorrectly.
1 #include "unpthread.h"
2 #define NLOOP 5000
3 int counter; /* incremented by threads */
4 void *doit(void *);
5 int
6 main(int argc, char **argv)
7{
8 pthread_t tidA, tidB;
9 pthread_create(&tidA, NULL, &doit, NULL);
10 pthread_create(&tidB, NULL, &doit, NULL);
11 /* wait for both threads to terminate */
12 pthread_join(tidA, NULL);
13 pthread_join(tidB, NULL);
14 exit(0);
15 }
16 void *
17 doit(void *vptr)
18 {
19 int i, val;
20 /*
21 * Each thread fetches, prints, and increments the counter NLOOP times.
22 * The value of the counter should increase monotonically.
23 */
24 for (i = 0; i < NLOOP; i++) {
25 val = counter;
26 printf("%d: %d\n", pthread_self(), val + 1);
27 counter = val + 1;
28 }
29 return (NULL);
30 }

Multiple threads updating a shared variable, is the simplest problem. The solution is to
protect the shared variable with a mutex (which stands for "mutual exclusion") and access the
variable only when we hold the mutex. In terms of pthreads, a mutex is a variable of type
pthread_mutex_t. We lock and unlock a mutex using the following two functions:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t * mptr);
int pthread_mutex_unlock(pthread_mutex_t * mptr);
Both return: 0 if OK, positive Exxx value on error
If we try to lock a mutex that is already locked by some other thread, we are blocked until the
mutex is unlocked.
If a mutex variable is statically allocated, we must initialize it to the constant
PTHREAD_MUTEX_INITIALIZER. We allocate a mutex in shared memory; we must initialize
it at runtime by calling the pthread_mutex_init function.
Some systems define PTHREAD_MUTEX_INITIALIZER to be 0, so omitting this
initialization is acceptable, since statically allocated variables are automatically initialized to 0.
But there is no guarantee that this is acceptable and other systems define the initializer to be
nonzero.
The following program is a corrected version of the above program that uses a single
mutex to lock the counter between the two threads.
1 #include "unpthread.h"
2 #define NLOOP 5000
3 int counter; /* incremented by threads */
4 pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
5 void *doit(void *);
6 int
7 main(int argc, char **argv)
8{
9 pthread_t tidA, tidB;
10 Pthread_create(&tidA, NULL, &doit, NULL);
11 Pthread_create(&tidB, NULL, &doit, NULL);
12 /* wait for both threads to terminate */
13 Pthread_join(tidA, NULL);
14 Pthread_join(tidB, NULL);
15 exit(0);
16 }
17 void *
18 doit(void *vptr)
19 {
20 int i, val;
21 /*
22 * Each thread fetches, prints, and increments the counter NLOOP times.
23 * The value of the counter should increase monotonically.
24 */
25 for (i = 0; i < NLOOP; i++) {
26 Pthread_mutex_lock(&counter_mutex);
27 val = counter;
28 printf("%d: %d\n", pthread_self(), val + 1);
29 counter = val + 1;
30 Pthread_mutex_unlock(&counter_mutex);
31 }
32 return (NULL);
33 }
We declare a mutex named counter_mutex and this mutex must be locked by the thread
before the thread manipulates the counter variable. When we run this program, the output is
always correct.
6. Condition variables
A mutex is fine to prevent simultaneous access to a shared variable, but we need
something else to let us go to sleep waiting for some condition to occur. Let's demonstrate this
with an example. We cannot call the pthread function until we know that a thread has
terminated. We first declare a global variable that counts the number of terminated threads and
protect it with a mutex.
int ndone; /* number of terminated threads */
pthread_mutex_t ndone_mutex = PTHREAD_MUTEX_INITIALIZER;
We then require that each thread increment this counter when it terminates, being careful to use
the associated mutex.
void * do_get_read (void *vptr)
{
...
pthread_mutex_lock(&ndone_mutex);
ndone++;
pthread_mutex_unlock(&ndone_mutex);
return(fptr); /* terminate thread */
}
It needs to lock the mutex continually and check if any threads have terminated.
while (nlefttoread > 0)
{
while (nconn < maxnconn && nlefttoconn > 0)
{
/* find a file to read */
}
/* See if one of the threads is done */
pthread_mutex_lock(&ndone_mutex);
if (ndone > 0)
{
for (i = 0; i < nfiles; i++)
{
if (file[i].f_flags & F_DONE)
{
pthread_join(file[i].f_tid, (void **) &fptr);

/* update file[i] for terminated thread */


...
}
}
}
pthread_mutex_unlock(&ndone_mutex);
}
While this is okay, it means the main loop never goes to sleep; it just loops, checking
ndone every time around the loop. This is called polling and is considered a waste of CPU time.
We want a method for the main loop to go to sleep until one of its threads notifies it that
something is ready. A condition variable, in conjunction with a mutex, provides this facility. The
mutex provides mutual exclusion and the condition variable provides a signaling mechanism.
In terms of pthreads, a condition variable is a variable of type pthread_cond_t. They are
used with the following two functions:
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);
int pthread_cond_signal(pthread_cond_t *cptr);
Both return: 0 if OK, positive Exxx value on error
The term "signal" in the second function's name does not refer to a Unix SIGxxx signal.
An example is the easiest way to explain these functions. Returning to our Web client
example, the counter ndone is now associated with both a condition variable and a mutex.
int ndone;
pthread_mutex_t ndone_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t ndone_cond = PTHREAD_COND_INITIALIZER;
A thread notifies the main loop that it is terminating by incrementing the counter while its mutex
lock is held and by signaling the condition variable.
pthread_mutex_lock(&ndone_mutex);
ndone++;
pthread_cond_signal(&ndone_cond);
pthread_mutex_unlock(&ndone_mutex);
The main loop then blocks in a call to pthread_cond_wait, waiting to be signaled by a
terminating thread.
while (nlefttoread > 0)
{
while (nconn < maxnconn && nlefttoconn > 0)
{
/* find file to read */
...
}
/* Wait for thread to terminate */
pthread_mutex_lock(&ndone_mutex);
while (ndone == 0)
pthread_cond_wait (&ndone_cond, &ndone_mutex);
for (i = 0; i < nfiles; i++)
{
if (file[i].f_flags & F_DONE)
{
pthread_join(file[i].f_tid, (void **) &fptr);
/* update file[i] for terminated thread */
...
}
}
pthread_mutex_unlock (&ndone_mutex);
}
Notice that the variable ndone is still checked only while the mutex is held. Then, if
there is nothing to do, pthread_cond_wait is called. This puts the calling thread to sleep and
releases the mutex lock it holds. Furthermore, when the thread returns from pthread_cond_wait,
the thread again holds the mutex.
Why is a mutex always associated with a condition variable?
The "condition" is normally the value of some variable that is shared between the threads.
The mutex is required to allow this variable to be set and tested by the different threads. For
example, if we did not have the mutex in the example code just shown, the main loop would test
it as follows:
/* Wait for thread to terminate */
while (ndone == 0)
pthread_cond_wait(&ndone_cond, &ndone_mutex);
But, there is a possibility that the last of the threads increments ndone after the test of ndone ==
0, but before the call to pthread_cond_wait. If this happens, this last "signal" is lost and the main
loop would block forever, waiting for something that will never occur again.
This is the same reason that pthread_cond_wait must be called with the associated mutex locked,
and why this function unlocks the mutex and puts the calling thread to sleep as a single, atomic
operation. If this function did not unlock the mutex and then lock it again when it returns, the
thread would have to unlock and lock the mutex and the code would look like the following:
/* Wait for thread to terminate */
pthread_mutex_lock(&ndone_mutex);
while (ndone == 0) {
pthread_mutex_unlock(&ndone_mutex);
pthread_cond_wait(&ndone_cond, &ndone_mutex);
pthread_mutex_lock(&ndone_mutex);
}
But again, there is a possibility that the final thread could terminate and increment the value of
ndone between the call to pthread_mutex_unlock and pthread_cond_wait.
Normally, pthread_cond_signal awakens one thread that is waiting on the condition variable.
There are instances when a thread knows that multiple threads should be awakened, in which
case, pthread_cond_broadcast will wake up all threads that are blocked on the condition variable.
#include <pthread.h>
int pthread_cond_broadcast (pthread_cond_t * cptr);
int pthread_cond_timedwait (pthread_cond_t * cptr, pthread_mutex_t *mptr, const
struct timespec *abstime);
Both return: 0 if OK, positive Exxx value on error
pthread_cond_timedwait lets a thread place a limit on how long it will block. abstime is a
timespec that specifies the system time when the function must return, even if the condition
variable has not been signaled yet. If this timeout occurs, ETIME is returned.
7. Raw sockets
Raw sockets provide three features not provided by normal TCP and UDP sockets:
Raw sockets allow us read and write ICMPv4, IGMPv4, and ICMPv6 packets. The ping
program, for example, sends ICMP echo requests and receives ICMP echo replies. This
capability also allows applications that are built using ICMP or IGMP to be handled
completely as user processes, instead of putting more code into the kernel.
With a raw socket, a process can read and write IPv4 datagrams with an IPV4 protocol
field that is not processed by the kernel. Most kernels only process datagrams containing
values of 1 (ICMP), 2 (IGMP), 6 (TCP), and 17 (UDP). But many other values are defined
for the protocol field: The IANA's "Protocol Numbers" registry lists all the values. For
example, the OSPF routing protocol does not use TCP or UDP, but it uses IP directly,
setting the protocol field of the IP datagram to 89. The gated program that implements
OSPF must use a raw socket to read and write these IP datagrams since they contain a
protocol field the kernel knows nothing about. This capability carries over to IPv6 also.
With a raw socket, a process can build its own IPv4 header using the IP_HDRINCL socket
option.
i. Raw socket creation
The steps involved in creating a raw socket are as follows:
1. The socket function creates a raw socket when the second argument is SOCK_RAW. The
third argument (the protocol) is normally nonzero. For example, to create an IPv4 raw
socket we would write
int sockfd;
sockfd = socket(AF_INET, SOCK_RAW, protocol);
where protocol is one of the constants, IPPROTO_xxx, defined by including the
<netinet/in.h> header, such as IPPROTO_ICMP. Only the superuser can create a raw
socket. This prevents normal users from writing their own IP datagrams to the network.
2. The IP_HDRINCL socket option can be set as follows:
const int on = 1;
if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0)
error
3. bind can be called on the raw socket, but this is rare. This function sets only the local
address: There is no concept of a port number with a raw socket. With regard to output,
calling bind sets the source IP address that will be used for datagrams sent on the raw
socket. If bind is not called, the kernel sets the source IP address to the primary IP
address of the outgoing interface.
4. connect can be called on the raw socket, but this is rare. This function sets only the
foreign address: Again, there is no concept of a port number with a raw socket. With
regard to output, calling connect lets us call write or send instead of sendto, since the
destination IP address is already specified.
ii. Raw socket output
Output on a raw socket is governed by the following rules:
1. Normal output is performed by calling sendto or sendmsg and specifying the destination IP
address. write, writev, or send can also be called if the socket has been connected.
2. If the IP_HDRINCL option is not set, the starting address of the data for the kernel to send
specifies the first byte following the IP header because the kernel will build the IP header
and prepend it to the data from the process. The kernel sets the protocol field of the IPv4
header that it builds to the third argument from the call to socket.
3. If the IP_HDRINCL option is set, the starting address of the data for the kernel to send
specifies the first byte of the IP header. The amount of data to write must include the size
of the caller's IP header. The process builds the entire IP header, except:
(i) The IPv4 identification field can be set to 0, which tells the kernel to set this value;
(ii) The kernel always calculates and stores the IPv4 header checksum; and
(iii) IP options may or may not be included;
4. The kernel fragments raw packets that exceed the outgoing interface Maximum
Transmission Unit (MTU).
IPv6 Differences
There are a few differences with raw IPv6 sockets:
All fields in the protocol headers sent or received on a raw IPv6 socket are in network byte
order.
There is nothing similar to the IPv4 IP_HDRINCL socket option with IPv6. Complete IPv6
packets cannot be read or written on an IPv6 raw socket. Almost all fields in an IPv6
header and all extension headers are available to the application through socket options.
Checksums on raw IPv6 sockets are handled differently.
IPV6_CHECKSUM Socket Option
For a raw IPv6 sockets, a socket option tells the kernel whether to calculate and store a
checksum in outgoing packets and verify the checksum in received packets. By default, this
option is disabled, and it is enabled by setting the option value to a nonnegative value, as in
int offset = 2;
if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_CHECKSUM,&offset, sizeof(offset)) < 0)
error
This not only enables checksums on this socket, it also tells the kernel the byte offset of
the 16-bit checksum: 2 bytes from the start of the application data in this example. To disable the
option, it must be set to -1. When enabled, the kernel will calculate and store the checksum for
outgoing packets sent on the socket and also verify the checksums for packets received on the
socket.
iii. Raw socket input
The first question that we must answer regarding raw socket input is: Which received IP
datagrams does the kernel pass to raw sockets? The following rules apply:
Received UDP packets and received TCP packets are never passed to a raw socket. If a
process wants to read IP datagrams containing UDP or TCP packets, the packets must be
read at the datalink layer.
Most ICMP packets are passed to a raw socket after the kernel has finished processing the
ICMP message.
All IGMP packets are passed to a raw socket after the kernel has finished processing the
IGMP message.
All IP datagrams with a protocol field that the kernel does not understand are passed to a
raw socket. The only kernel processing done on these packets is the minimal verification of
some IP header fields: the IP version, IPv4 header checksum, header length, and
destination IP address.
If the datagram arrives in fragments, nothing is passed to a raw socket until all fragments
have arrived and have been reassembled.
When the kernel has an IP datagram to pass to the raw sockets, all raw sockets for all processes
are examined, looking for all matching sockets. A copy of the IP datagram is delivered to each
matching socket. The following tests are performed for each raw socket and only if all three tests
are true is the datagram delivered to the socket:
If a nonzero protocol is specified when the raw socket is created, then the received
datagram's protocol field must match this value or the datagram is not delivered to this
socket.
If a local IP address is bound to the raw socket by bind, then the destination IP address of
the received datagram must match this bound address or the datagram is not delivered to
this socket.
If a foreign IP address was specified for the raw socket by connect, then the source IP
address of the received datagram must match this connected address or the datagram is not
delivered to this socket.
Notice that if a raw socket is created with a protocol of 0, and neither bind nor connect is
called, then that socket receives a copy of every raw datagram the kernel passes to raw sockets.
Whenever a received datagram is passed to a raw IPv4 socket, the entire datagram, including
the IP header, is passed to the process. For a raw IPv6 socket, only the payload is passed to the
socket.
8. Ping Program
The ping program allows us to test whether another host is reachable in the computer
network. The ping program that works with both IPv4 and IPv6. We will develop our own
program instead of presenting the publicly available source code for two reasons. First, the
publicly available ping program suffers from a common programming disease known as creeping
featurism: It supports a dozen different options. Second, the public version works only with IPv4
and we want to show a version that also supports IPv6.
The operation of ping is extremely simple: An ICMP echo request is sent to some IP
address and that node responds with an ICMP echo reply. These two ICMP messages are
supported under both IPv4 and IPv6. The following fig. shows the format of the ICMP
messages.
Figure : Format of ICMPv4 and ICMPv6 echo request and echo reply messages.

The type specifies the type values for these messages and the code is 0. The identifier
represents the PID of the ping process and we increment the sequence number by one for each
packet we send. We store the 8-byte timestamp of when the packet is sent as the optional data.
The rules of ICMP require that the identifier, sequence number, and any optional data be
returned in the echo reply. Storing the timestamp in the packet allows us calculate the RTT when
the reply is received.
The following figure is an overview of the function that comprise ping program.
The program operates in two parts: One half reads everything received on a raw socket,
printing the ICMP echo replies, and the other half sends an ICMP echo request once per second.
The second half is driven by a SIGALRM signal once per second.
The following programs shows our ping.h header that is included by all our program files.
#include "unp.h"
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#define BUFSIZE 1500
char sendbuf[BUFSIZE];
int datalen; /* #bytes of data following ICMP header */
char *host;
int nsent; /* add 1 for each sendto() */
pid_t pid; /* our PID */
int sockfd;
int verbose;
void proc_v4(char *, ssize_t, struct msghdr *, struct timeval *);
void proc_v6(char *, ssize_t, struct msghdr *, struct timeval *);
void send_v4(void);
void send_v6(void);
void readloop(void);
void sig_alrm(int);
void tv_sub(struct timeval *, struct timeval *);
struct proto
{
void (*fproc) (char *, ssize_t, struct msghdr *, struct timeval *);
void (*fsend) (void);
struct sockaddr *sasend; /* sockaddr{} for send, from getaddrinfo */
struct sockaddr *sarecv; /* sockaddr{} for receiving */
socklen_t salen; /* length of sockaddr {}s */
int icmpproto; /* IPPROTO_xxx value for ICMP */
} *pr;
#ifdef IPV6
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#endif
Include IPv4 and ICMPv4 headers
We include the basic IPv4 and ICMPv4 headers; define some global variables, and our
function prototypes.
Define proto structure
We use the proto structure to handle the difference between IPv4 and IPv6. This structure
contains two function pointers, two pointers to socket address structures, the size of the socket
address structures, and the protocol value for ICMP. The global pointer pr will point to one of
the structures that we will initialize for either IPv4 or IPv6.
Include IPv6 and ICMPv6 headers
We include two headers that define the IPv6 and ICMPv6 structures and constants.
The main function is shown in the program
#include "ping.h"
struct proto proto_v4 = { proc_v4, send_v4, NULL, NULL, 0, IPPROTO_ICMP };
#ifdef IPV6
struct proto proto_v6 = { proc_v6, send_v6, NULL, NULL, 0, IPPROTO_ICMPV6 };
#endif
int datalen = 56; /* data that goes with ICMP echo request */
int main(int argc, char **argv)
{
int c;
struct addrinfo *ai;
char *h;
opterr = 0; /* don't want getopt() writing to stderr */
while ( (c = getopt (argc, argv, "v") ) != -1)
{
switch (c)
{
case 'v':
verbose++;
break;
case '?':
err_quit ("unrecognized option: %c", c);
}
}
if (optind != argc - 1)
err_quit ("usage: ping [ -v ] <hostname>");
host = argv [optind];
pid = getpid() & Oxffff; /* ICMP ID field is 16 bits */
signal(SIGALRM, sig_alrm);
ai = host_serv (host, NULL, 0, 0);
h = sock_ntop_host(ai->ai_addr, ai->ai_addrlen);
printf ("PING %s (%s): %d data bytes\n", ai->ai_canonname ? ai->ai_canonname : h, h,
datalen);
/* initialize according to protocol */
if (ai->ai_family == AF_INET)
{
pr = &proto_v4;
#ifdef IPV6
}
else if (ai->ai_family == AF_INET6)
{
pr = &proto_v6;
if (IN6_IS_ADDR_V4MAPPED (&(((struct sockaddr_in6 *) ai->ai_addr)->sin6_addr)))
err_quit ("cannot ping IPv4-mapped IPv6 address");
#endif
}
else
err_quit ("unknown address family %d", ai->ai_family);
pr->sasend = ai->ai_addr;
pr->sacrecv = Calloc (1, ai->ai_addrlen);
pr->salen = ai->ai_addrlen);
readloop();
exit(0);
}
Define proto structures for IPv4 and IPv6
We define a proto structure for IPv4 and IPv6. The socket address structure pointers are
initialized to null pointers.
Length of optional data
We set the amount of optional data that gets sent with the ICMP echo request to 56 bytes.
Any data that come with an echo request must be sent back in the echo reply. We will store the
time at which we send an echo request in the first 8 bytes of this data area and then use this to
calculate and print the RTT when the echo reply is received.
Handle command-line options
The only command-line option we support is -v, which will cause us to print most
received ICMP messages. A signal handler is established for SIGALRM, and we will see that
this signal is generated once per second and causes an ICMP echo request to be sent.
Process hostname argument
A hostname or IP address string is a required argument and it is processed by our
host_serv function. The returned addrinfo structure contains the protocol family, either
AF_INET or AF_INET6. We initialize the pr global to the correct proto structure. We also make
sure that an IPv6 address is not really an IPv4-mapped IPv6 address by calling
IN6_IS_ADDR_V4MAPPED, because even though the returned address is an IPv6 address,
IPv4 packets will be sent to the host. The socket address structure that has already been allocated
by the getaddrinfo function is used as the one for sending, and another socket address structure
of the same size is allocated for receiving.
readloop function.
#include "ping.h"
void readloop(void)
{
int size;
char recvbuf[BUFSIZE];
ssize_t n;
struct timeval tval;
sockfd = socket(pr->sasend->sa_family, SOCK_RAW, pr->icmpproto);
setuid(getuid()); /* don't need special permissions any more */
size = 60 * 1024; /* OK if setsockopt fails */
setsockopt (sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof (size));
sig_alrm (SIGALRM); /* send first packet */
for ( ; ; ) {
len = pr->salen;
n = recvfrom (sockfd,recvbuf, sizeof(recvbuf),0,pr->sarecv,&len);
if (n < 0) {
if (errno == EINTR)
continue;
else
err_sys("recvmsg error");
}
gettimeofday (&tval, NULL);
(*pr->fproc) (recvbuf, n, &msg, &tval);
}
}
Create socket
A raw socket of the appropriate protocol is created. The call to setuid sets our effective
user ID to our real user ID, in case the program was set-user-ID instead of being run by root.
Set socket receive buffer size
We try to set the socket receive buffer size to 61,440 bytes (60 x 1024), which should be
larger than the default.
Send first packet
We call our signal handler, which we will see sends a packet and schedules a SIGALRM
for one second in the future.
Infinite loop reading all ICMP messages
The main loop of the program is an infinite loop that reads all packets returned on the raw
ICMP socket. We call gettimeofday to record the time that the packet was received and then call
the appropriate protocol function (proc_v4 or proc_v6) to process the ICMP message.
proc_v4 function: processes ICMPv4 message.
#include "ping.h"
void proc_v4 (char *ptr, ssize_t len, struct msghdr *msg, struct timeval *tvrecv)
{
int hlenl, icmplen;
double rtt;
struct ip *ip;
struct icmp *icmp;
struct timeval *tvsend;
ip = (struct ip *) ptr; /* start of IP header */
hlenl = ip->ip_hl << 2; /* length of IP header */
if (ip->ip_p != IPPROTO_ICMP)
return; /* not ICMP */
icmp = (struct icmp *) (ptr + hlenl); /* start of ICMP header */
if ( (icmplen = len - hlenl) < 8)
return; /* malformed packet */
if (icmp->icmp_type == ICMP_ECHOREPLY) {
if (icmp->icmp_id != pid)
return; /* not a response to our ECHO_REQUEST */
if (icmplen < 16)
return; /* not enough data to use */
tvsend = (struct timeval *) icmp->icmp_data;
tv_sub (tvrecv, tvsend);
rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0;
printf ("%d bytes from %s: seq=%u, ttl=%d, rtt=%.3f ms\n",
icmplen, Sock_ntop_host (pr->sarecv, pr->salen),
icmp->icmp_seq, ip->ip_ttl, rtt);
} else if (verbose) {
printf (" %d bytes from %s: type = %d, code = %d\n",
icmplen, Sock_ntop_host (pr->sarecv, pr->salen),
icmp->icmp_type, icmp->icmp_code);
}
}
Figure: Headers, pointers, and lengths in processing ICMPv4 reply

proc_v6 function: processes received ICMPv6 message.


#include "ping.h"
void proc_v6 (char *ptr, ssize_t len, struct msghdr *msg, struct timeval *tvrecv)
{
#ifdef IPV6
int hlen1, icmplen;
double rtt;
struct icmp6_hdr *icmp6;
struct timeval *tvsend;
icmp6 = (struct icmp6_hdr *) ptr;
if (len < 8)
return; /* malformed packet */
if (icmp6->icmp6_type == ICMP6_ECHO_REPLY) {
if (icmp6->icmp6_id != pid)
return; /* not a response to our ECHO_REQUEST */
if (len < 16)
return; /* not enough data to use */
tvsend = (struct timeval *) (icmp6 + 1);
tv_sub (tvrecv, tvsend);
rtt = tvrecv->tv_sec * 1000.0 + tvrecv->tv_usec / 1000.0;
hlim = -1;
for (cmsg = CMSG_FIRSTHDR (msg); cmsg != NULL;
cmsg = CMSG_NXTHDR (msg, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IPV6
&& cmsg->cmsg_type == IPV6_HOPLIMIT) {
hlim = * (u_int32_t *) CMSG_DATA (cmsg);
break;
}
}
printf("%d bytes from %s: seq=%u, hlim=",
len, Sock_ntop_host (pr->sarecv, pr->salen), icmp6->icmp6_seq);
if (hlim == -1)
printf("???"); /* ancillary data missing */
else
printf("%d", hlim);
printf(", rtt=%.3f ms\n", rtt);
} else if (verbose) {
printf(" %d bytes from %s: type = %d, code = %d\n",
len, Sock_ntop_host (pr->sarecv, pr->salen),
icmp6->icmp6_type, icmp6->icmp6_code);
}
#endif /* IPV6 */
}
send_v4 function: builds an ICMPv4 echo request message and sends it.
#include "ping.h"
void send_v4 (void)
{
int len;
struct icmp *icmp;
icmp = (struct icmp *) sendbuf;
icmp->icmp_type = ICMP_ECHO;
icmp->icmp_code = 0;
icmp->icmp_id = pid;
icmp->icmp-seq = nsent++;
memset (icmp->icmp_data, 0xa5, datalen); /* fill with pattern */
Gettimeofday ((struct timeval *) icmp->icmp_data, NULL);
len = 8 + datalen; /* checksum ICMP header and data */
icmp->icmp_cksum = 0;
icmp->icmp_cksum = in_cksum ((u_short *) icmp, len);
sendto (sockfd, sendbuf, len, 0, pr->sasend, pr->salen);
}
send_v6 function: builds and sends an ICMPv6 echo request message.
#include "ping.h"
void send_v6 ()
{
#ifdef IPV6
int len;
struct icmp6_hdr *icmp6;
icmp6 = (struct icmp6_hdr *) sendbuf;
icmp6->icmp6_type = ICMP6_ECHO_REQUEST;
icmp6->icmp6_code = 0;
icmp6->icmp6_id = pid;
icmp6->icmp6_seq = nsent++;
memset ((icmp6 + 1), 0xa5, datalen); /* fill with pattern */
Gettimeofday ((struct timeval *) (icmp6 + 1), NULL);
len = 8 + datalen; /* 8-byte ICMPv6 header */
sendto (sockfd, sendbuf, len, 0, pr->sasend, pr->salen);
/* kernel calculates and stores checksum for us */
#endif /* IPV6 */
}
9. Traceroute program.
Traceroute lets us determine the path that IP datagrams follow from our host to some
other destination. Its operation is simple. Traceroute uses the IPv4 TTL field or the IPv6 hop
limit field and two ICMP messages. It starts by sending a UDP datagram to the destination with a
TTL (or hop limit) of 1. This datagram causes the first-hop router to return an ICMP "time
exceeded in transit" error. The TTL is then increased by one and another UDP datagram is sent,
which locates the next router in the path. When the UDP datagram reaches the final destination,
the goal is to have that host return an ICMP "port unreachable" error. This is done by sending the
UDP datagram to a random port that is (hopefully) not in use on that host.
trace.h header.
#include "unp.h"
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/udp.h>
#define BUFSIZE 1500
struct rec { /* of outgoing UDP data */
u_short rec_seq; /* sequence number */
u_short rec_ttl; /* TTL packet left with */
struct timeval rec_tv; /* time packet left */
};
/* globals */
char recvbuf [BUFSIZE];
char sendbuf [BUFSIZE];
int datalen; /* # bytes of data following ICMP header */
char *host;
u_short sport, dport;
int nsent; /* add 1 for each sendto () */
pid_t pid; /* our PID */
int probe, nprobes;
int sendfd, recvfd; /* send on UDP sock, read on raw ICMP sock */
int ttl, max_ttl;
int verbose;
/* function prototypes */
const char *icmpcode_v4 (int);
const char *icmpcode_v6 (int);
int recv_v4 (int, struct timeval *);
int recv_v6 (int, struct timeval *);
void sig_alrm (int);
void traceloop (void);
void tv_sub (struct timeval *, struct timeval *);
struct proto {
const char *(*icmpcode) (int);
int (*recv) (int, struct timeval *);
struct sockaddr *sasend; /* sockaddr{} for send, from getaddrinfo */
struct sockaddr *sarecv; /* sockaddr{} for receiving */
struct sockaddr *salast; /* last sockaddr{} for receiving */
struct sockaddr *sabind; /* sockaddr{} for binding source port */
socklen_t salen; /* length of sockaddr{}s */
int icmpproto; /* IPPROTO_xxx value for ICMP */
int ttllevel; /* setsockopt () level to set TTL */
int ttloptname; /* setsockopt () name to set TTL */
} *pr;
#ifdef IPV6
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#endif
We include the standard IPv4 headers that define the IPv4, ICMPv4, and UDP structures
and constants. The rec structure defines the data portion of the UDP datagram that we send, but
we will see that we never need to examine this data. It is sent mainly for debugging purposes.
Define proto structure
We handle the protocol differences between IPv4 and IPv6 by defining a proto structure
that contains function pointers, pointers to socket address structures, and other constants that
differ between the two IP versions. The global pr will be set to point to one of these structures
that is initialized for either IPv4 or IPv6, after the destination address is processed by the main
function.
Include IPv6 headers
We include the headers that define the IPv6 and ICMPv6 structures and constants.
main function for traceroute program.
#include "trace.h"
struct proto proto_v4 = { icmpcode_v4, recv_v4, NULL, NULL, NULL, NULL, 0,
IPPROTO_ICMP, IPPROTO_IP, IP_TTL
};
#ifdef IPV6
struct proto proto_v6 = { icmpcode_v6, recv_v6, NULL, NULL, NULL, NULL, 0,
IPPROTO_ICMPV6, IPPROTO_IPV6, IPV6_UNICAST_HOPS
};
#endif
int datalen = sizeof (struct rec); /* defaults */
int max_ttl = 30;
int nprobes = 3;
u_short dport = 32768 + 666;
int main(int argc, char **argv)
{
int c;
struct addrinfo *ai;
char *h;
opterr = 0; /* don't want getopt () writing to stderr */
while ( (c = getopt (argc, argv, "m:v")) != -1) {
switch (c) {
case 'm':
if ( (max_ttl = atoi (optarg)) <= 1)
err_quit ("invalid -m value");
break;
case 'v':
verbose++;
break;
case '?':
err_quit ("unrecognized option: %c", c);
}
}
if (optind != argc - 1)
err_quit ("usage: traceroute [ -m <maxttl> -v ] <hostname>");
host = argv [optind];
pid = getpid();
Signal (SIGALRM, sig_alrm);
ai = Host_serv (host, NULL, 0, 0);
h = Sock_ntop_host (ai->ai_addr, ai->ai_addrlen);
printf ("traceroute to %s (%s) : %d hops max, %d data bytes\n",
ai->ai_canonname ? ai->ai_canonname : h, h, max_ttl, datalen);
/* initialize according to protocol */
if (ai->ai_family == AF_INET) {
pr = &proto_v4;
#ifdef IPV6
} else if (ai->ai_family == AF_INET6) {
pr = &proto_v6;
if (IN6_IS_ADDR_V4MAPPED
(&(((struct sockaddr_in6 *) ai->ai_addr)->sin6_addr)))
err_quit ("cannot traceroute IPv4-mapped IPv6 address");
#endif
} else
err_quit ("unknown address family %d", ai->ai_family);
pr->sasend = ai->ai_addr; /* contains destination address */
pr->sarecv = Calloc (1, ai->ai_addrlen);
pr->salast = Calloc (1, ai->ai_addrlen);
pr->sabind = Calloc (1, ai->ai_addrlen);
pr->salen = ai->ai_addrlen;
traceloop();
exit (0);
}
Define proto structures
We define the two proto structures, one for IPv4 and one for IPv6, although the pointers
to the socket address structures are not allocated until the end of this function.
Set defaults
The maximum TTL or hop limit that the program uses defaults to 30, although we
provide the -m command-line option to allow the user change this. For each TTL, we send three
probe packets, but this could be changed with another command-line option. The initial
destination port is 32768+666, which will be incremented by one each time we send a UDP
datagram.
Process command-line arguments
The -v command-line option causes most received ICMP messages to be printed.
Process hostname or IP address argument and finish initialization
The destination hostname or IP address is processed by our host_serv function, returning
a pointer to an addrinfo structure. Depending on the type of returned address, IPv4 or IPv6, we
finish initializing the proto structure, store the pointer in the pr global, and allocate additional
socket address structures of the correct size.
PART A
1. What do you mean by threaded server? [May 07]
The threaded server is probably the simplest real server type. A server program almost
always needs to handle more than one connection at a time. The reason is, since each
connection gets its own thread, each thread can use simple blocking I/O on the socket. Each
thread handles its connection more or less identically to the way the basic server handles
each connection.
Benefits
Less time to create a new thread
Less time to terminate a thread than a process.
Less communication overheads.
2. Mention the purpose of Ping program. [Nov 07]
Ping program allows us to test whether another host is reachable.
The program sends an ICMP echo request message to a host, expecting an ICMP
echo reply to be returned.
3. Differentiate between ping and trace route program. [May 07]
Sl. No Ping Program Trace route Program
Traceroute allows us see the route that IP
Ping program allows us to test
datagrams follow from one host to another.
1. whether another host is
trace route also allows us use the IP source
reachable.
route option.
The program sends an ICMP Traceroute uses ICMP and the TTL field in the
echo request message to a host, IP header. The purpose of the TTL field is to
2.
expecting an ICMP echo reply prevent datagrams from ending up in infinite
to be returned. loops.
The Ping program was written The trace route program was written by Van
3.
by Mike Muuss Jacobson.

4. How the server knows about the version of the communicating client? [Apr 08]
The Ethernet header type field value of 0x0800 indicates IPv4 frame and 0x86dd
indicates IPv6 frame.
5. How the ping program is operated?
The operation of ping is extremely simple: An ICMP echo request is sent to some IP
address and that node responds with an ICMP echo reply. These two ICMP messaes are
supported under both IPv4 and IPv6.
The program operates in two parts: One half reads everything received on a raw socket,
printing the ICMP echo replies, and the other half sends an ICMP echo request once per
second.
6. List out the benefits of IPv6.
Address depletion solved
International misallocation solved
End to End communication restored
Scoped addresses & address selection possible
More efficient forwarding and fast routing
Built in security and mobility.
7. What are the rules of Dual – stack host in dealing with listening socket.
A listening IPv4 socket accept incoming connections from IPv4 clients
If a server has a IPv6 listening socket which has wild card address, then the socket
can accept incoming connections from either IPv4 or IPv6.
8. What are the basic functions related to threads?
pthread_create ( )
pthread_join ( )
pthread_self( )
pthread_detach ( )
pthread_exit ( )
9. What are the attributes of threads?
Each thread has following attributes.
Its priority
Its initial stack size
Whether it should be a daemon thread or not.
10. What are the advantages of threads?
Threads have some advantages of processes. They take:
Less time to create a new thread than a process, because the newly created thread uses
the current process address space.
Less time to terminate a thread than a process.
Less time to switch between two threads within the same process, partly because the
newly created thread uses the current process address space.
Less communication overheads – communicating between the threads of one process
is simple because the threads share everything: address space, in particular. So, data
produced by one thread is immediately available to all other threads.
11. What is the disadvantage of threads?
All threads within a process share the same global memory. This makes the sharing of
information easy between the threads, but along with this simplicity comes the problem of
synchronization.
PART B
1. Explain about thread creation and termination [May 07]
Definition –Thread
Header file: #include<pthread.h>
Thread creation syntax- pthread_create Function
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func)(void
*), void *arg);
pthread_join()Function- Suspend the execution of calling thread until the target tid
terminates
int pthread_join(pthread_t tid,void ** status)
pthread_self Function-Return its own thread ID.
pthread_self()
pthread_detach Function- When detached thread terminates all its resources are released
and need not wait for it to terminate
pthread_detach(pthread_t tid)
pthread_exit Function- Terminate a thread
pthread_exit(pthread_t tid)
2. Elaborate about raw socket creation, input and output. [10, Nov 07, Apr 08]
Raw sockets bypassing transport layer
Raw socket creation
Socket creation function socket(AF_INET,SOCK_RAW,protocol)
IP_HDRINCL
Bind can be called on raw socket, but this is rare.
Connect can be called on raw socket, but this is rare.
Raw socket output
Call sendto or sendmsg and write,writev or send for connected socket.
IP_HDRINCL is not set, kernel build and prepend the header to the data
IP_HDRINCL is set,amounrt of data includes the header.
Fragmentation
Raw socket input
Received TCP &UDP pkts are never passed to raw socket.
Most ICMP packets are passed
All IGMP packets
All IPdatagrams with protocol fields
Arrived fragments are reassembled
3. Explain the concept of IPV4 and IPV6 interoperability. [Nov 08]
IPV4 client and IPV6 server
Fig:IPV6 server on dual stack host serving IPV4 and IPV6 client
IPV6 client and IPV4 server
IPV6 address testing macros
Source code portability
4. Explain the concept of mutual exclusion. [Nov 07, May 07]
Mutex-Thread synchronization object used by threads to control access to shared
resources.
Sequence of steps
Create and initialize a mutex variable
Several threads attempts to lock the mutex
Onlu one succeeds & that owns the mutex
Owner performs some action
unlocks the mutex
Another thread access the mutex & performs its operation.
Mutex is destroyed.
Mutex functions.
#include<pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mptr)
int pthread_mutex_unlock(pthread_mutex_ *mptr)
Program:Two threads increments global variable using mutex.
5. Explain the operation of trace route program. [6, Nov 07, Nov 08, May 07]
trace route program allow us to determine the path that IP datagram follow from source
host to destination
Uses IPV4 TTL field or IPV6 hoplimit and 2 ICMP messages.
Trace.h header file
Standard IPV4 headers,ICMPV4, IPV4 and UDP structures.
Structure rec –defines the data portion of UDP datagram
structure proto
• Function pointers,pointer to socket address structures,constants
IPV6 & ICMPV6 structures and constants
Operation of trace route program
Main program
Trace loop function
Read ICMP messages
Print reply
6. Explain how a TCP echo server using threads is created and also give their advantages.
[Nov 07]
Create one thread per client instead of forking a child.
Program for TCP echo server using threads
tcp_listen function is used to make the protocol independent socket
pthread_create() to create a new thread
call the do it function to handle the client.
Passing argument to new threads
7. Condition Variables. [Nov 07]
Condition variable allows a thread to block its own execution until some shared data
reaches a particular state.
Used in conjunction with mutex.
Functions associated with Condition variables
int pthread_cond_wait(pthread_cond_t *cptr, pthread__mutex_t *mptr)-used to
wait until the condition is signaled or broadcasted.
int pthread_cond_signal(pthread__cond_t *cptr)-used to wake one thread that is
waiting on the condition variable.
int pthread_cond_timedwait(pthread__cond_t *cptr, pthread_mutex_t *mptr,const
struct timespec *abstime) used to limit the time that a thread waits for a condition
to be signaled.
Web client program using condition variable.
8. Compare IPV4 & IPV6
IPV4 IPV6

It supports 32 bit address It supports 128 bit address

Address is represented using dotted decimal Address is represented using colon hexa
notation.Eg:10.2.3.2 notation. Eg:0800:2078:e3e3
Header length is 20 bytes excluding variable Header length is 40 bytes
length options
TTL field is used to limit the IP data gram Hoplimit field is used to limit the IP data gram

Protocol field specifies the type of data Next header field specifies the type of data
contained in the IP datagram contained in the IP datagram
Does not support optional headers supports optional headers

supports Broadcasting Doesn’t support Broadcasting

Fragment ofgfset is used with fragmentation Separate header is used for this purpose, since
fragmentation is only performed by source
host
Header checksum field is used Header checksum field is omitted

Maximun size of IPV4 datagram is 65535 Maximun size of IPV4 datagram is 65575
bytes bytes

9. Explain the operation of ping program. [Nov 07]


Ping used to test whether a particular host is reachable across an internet protocol.
Ping sends ICMP echo request and waits for an ICMP response.
It measures round trip time(RTT)
Format of ICMP messages. ICMPV4 echo request and echo reply message format
Fig: overview of functions in ping program
Header file ping.h
Main program
Readloop() function
Proc_v4 and proc_v6 function
Send_v4 and send_v6 function
10. Write a C program that can generate an ICMPv4 echo request packet and process the
received ICMPv4 echo reply.
Refer Ping program.
UNIT V
Part A
1. Define PDU
A data unit that is composed of data, address information or control information that is
passed between Open System Interconnection (OSI) layers is a protocol stack. The general
message format in SNMPv1 is a ‘wrapper’ consisting of small header and an encapsulated
PDU.
2. In what way RMON is related to SNMP protocol.
The RMON specification is an extension of the SNMP Management Information Base
(MIB). RMON is specified as part of the MIB in RFC 1757 as an extension of SNMP.
3. Define RMON
It defines a remote monitoring MIB that supplements MIB-II and provides the network
manager with vital information about the inter – network. RMON extended the capabilities of
SNMP to include management of local area networks.
4. Mention the practical Issues in SMI.
Measurement – Manager will believe the reported values that could be erroneous.
Private MIBs – one of three different formats are used by the vendors
Limitations of MIB objects – The set of objects in the MIB are inadequate.
Part B
1. Define SNMP. Describe the architecture of SNMP.
Definition- SNMP refers to a set of standards for network management including
a protocol, a database structure specification and a set of data objects.
Architecture:
Goals of architecture
Elements of architecture
Scope of management information
Representation of management information
Operations supported on management information
Form and meaning of protocol exchanges
Definition of administrative relationship among management entities
Form and meaning of references to management information.
2. Explain the message format of SNMP.
SNMP message format
SNMPv1 general message format.
it has 3 fields.
Version, Community, PDU
Five types of PDU
SetRequest,GetNextRequest,setRequest,SetResponse,Trap
PDU formats.:SNMPv1 common PDU Format
Fields:PDU Type,Request ID,Error status,Error index,Variable bindings
SNMPv1 Trap PDU formats.
Fields:PDU Type,Enterprise,Agent Addr,generic Trap,Specific Trap,Time
stamp,Variable bindings
3. Describe the concept of standard MIBs.
MIB-I-collection of managed objects
Structure of management information
3 parts
1.Module definition,2.Object definition,3. Notification definition
MIB-II
second version of nMIB& subset if MIB-I
Criteria for defining MIB-II
MIB-II group - system group,interfaces group,at group,ip groupicmp
group,tcp group,udp group,transmission group and egp group.
Ethernet MIB.
4. Compare SNMPv2 and SNMPv3.
SNMPv2 SNMPv3
It supports 32 bit address It supports 128 bit address
Performs PDU processing Performs message processing
Security is not supported Has security and remote configuration
capabilities.Supports the concurrent use of
different security, access control and message
processing models.
Configure the SNMP agent using MIB objects Can dynamically configure the SNMP agent
that represent the agent’s configuration using SNMP SET commands.
Fixed length of engineID Variable length of engineID

4. Explain the architecture of SNMP entity and traditional SNMP manager as specified
in RFC2271. or explain the architecture of SNMPV3.
SNMPv3 Architecture-SNMP entity
Fig:Block diagram of SNMP entity
Dispatcher
Message processing subsystem
Security subsystem
Access control subsystem
Command generator and Responder
Notification Originator and Receiver
Proxy forwarder
Traditional SNMP manager
Fog:Block diagram
It has 3 Categories of application & SNMP engine
Command generator application, Notification originator application,
Notification receiver application.
SNMP engine
It has depatcher,message processing subsystem, security subsystem
Traditional SNMP agent
Block diagram
3 Categories of application is referred as SNMP agent
Command generator application, Notification originator application, proxy
forwarded application
Access control subsystem,
SNMPV3 architecture protects the following threats.
Modification of information,Masqurade, Disclosure,Denial of
service,Traffic analysis.
7. Limitations of SNMP
Not suitable for management of large network because of polling performance
limitations.
Not well suited for retrieving large amount of data
Traps are unacknowledged
It supports only trivial authenticationwhich is better for monitoring than control
Only way to trigger an event is to set a variable.
It does not support applications that make sophisticated management queries based on
object values or types
It doesn’t support manager-to-manager communication
8. Write notes on RMON.
Remote monitoring is a standard monitoring specification that enables various network
monitors and console systems to exchange network monitoring data.
Versions-RMON1(RMONv1)& RMON2(RMONV2)
Design goal of RMON
Offline operation,proactive monitoring,Problem detection and
reporting,Value added data,Multiple managers
Groups of RMON1 MIB
Statistics,History,Alarm,Hosts,HostTopN,Matrix,Filter,Capture,EventToken
ring
Groups of RMON2 MIB
Protocol directory,group,Protocol distribution,Address map,Network layer Host,
Network layer matrix group,application layer host,application layer matrix,User history,probe
configuration, RMON conformance
9. Trap Directed Polling
10. Proxy configuration.
11. Explain the data types in UNIVERSAL class of ASN.1 for SNMP MIB.
UNIVERSAL class of SNMP MIB consist of application independent data types
Integer, Ocetestring, Null, object identifier-Basic building blocks
sequence,sequence_of ->Constructor type used to construct tables
****************************

You might also like