From this graph we can see that polling allows for the processing of five times the number of packets as an interrupt driven implementation!
performance = robustness!
In implementing robustness to stop an attacker from driving the processing rate to zero, we also get better performance!
A denial of service attack is essentially an attack on performance. The attacker aims to bring the network performance to its knees. We can use a polling solution to solve this problem and improve on both the performance and robustness of the system. DoS attacks are commonly used for extortion purposes, frequently targeting weakly secured pornography and gambling websites.
Polling removes the interrupt rate from an attacker's control.
However, there is an important problem with polling, namely busy-waiting. There are several solutions available to solve this problem.
1. Perform polling at only system calls and timer interrupt. 2. Use polling exclusively which would result in latency. 3. Use a limited interrupt rate that only switches to polling when a particular threshold value of the interrupt rate is reached.
The third option is ideal.
The goal of networking is to provide access to the resources of multiple computers. This leads us to the Network Effect which states that the value of a network is proportional to the size of the network. An example of effect can be seen in using Google search which utilizes thousands of computers and large volumes of data to provide search results to your computer. This leads us to an obvious question. How can we best harness the power of these vast computing resources? What OS mechanisms provide easy, robust, well-performing access to these resources? A Client/Server Architecture, of course!
This mechanism limits communication and sets defined client and server roles. The client plays an active role in contacting the server and then making a request, while the server plays a passive role in waiting for client requests and responding accordingly. The advantages of using such an architecture are a clean connection pattern and modularity.
A clean connection pattern involves a client contacting a server, the client making a request, and finally the server responding to each request. Modularity is achieved because of the "req-resp" client-server interaction being similar to that of a system call interface. This allows for independent evolution of the client and server while still maintaining normal operation.
It is possible to implement a special file system for opening network conections.
open("/net/18.26.4.9/80",O_RDWR)
This was the goal of Plan 9, which was to be a successor to the UNIX OS. It aimed to completely fulfill the UNIX "Big Idea" by treating everything as a file stored in namespace.
Real network system calls use a socket abstraction.
socket(address family,protocol family,flags)
This creates a new networking-type file descriptor.
Clients
connect(): Creates an active connection.
Servers
listen(): Sets up a passive connection. accept(): Waits for a client connection and then returns a new connection for the client.
--- Eric Ooi 2006/06/01 23:00
Simple Server
This is an example of a very simple server. It first listens for the client and then accepts it giving it a fd. Then it receives data from the client through read and sends acknowledgement through the write. It then closes the client.
fd = listen();
while(1) {
client_fd = accept(fd);
read(client_fd, ...);
write(client_fd,...);
close(client_fd);
}
However, this server leaves itself open to attack in a number of ways.
1. Connect and then send no data - An attacker could open up a connection to the server, but then send no data. This would cause the server to block forever on the read. 2. Connect and send a request but don't allow response - An attacker could connect to the server and then send a request. But, it could then choose not to allow a response from the server. This deals with the flow control and again causes the server to be unable to service any other clients.
In both of these attacks the attacker exhausts the program count.
The idea for a multiprocess service is to have each client served by separate prcoess. This way only the malicious client that tries to take over the server gets blocked while other clients are still able to be served.
fd = listen();
while(1) {
client_fd = accept(fd);
p = fork();
if(p == 0) {
read(client_fd, ...);
write(client_fd, ...);
close(client_fd);
exit(0);
}
close(client_fd);
}
Here the simple server is made to fork every listen() finds a new client trying to talk to the server. This creates a new child process for each client. When the client is done, the child closes the socket and the parent closes the client fd.
Issues with this implementation
1. Many connections (DoS) - An attacker takes up all of the server resources through connecting with many clients. 2. Process are expensive - new processes are expensive and each client causes a new process to be created.
The output of a multiprocess service would look similar to this graph.
The idea here is to use threads instead of processes. fork -> pthread_create would be utilized so that each client would make a new thread.
The negative side is that there would be synchronization issues
The good thing is that resources are better utilized (shared address space) but it still isn't perfect.
--- Andrew Ahn
The sequence of operations is determine by the user's interaction (event)
Fcntl(fd, F_SETFL, O_NONBLOCK)
Fd = listen();
NOBLOCK
While (1) {
Cfd = connect(); //if (success) add connection fd, cfd.
For (each cfd) {
If (cfd is on request state)
Read(); //might gets respond
If (cfd is in respond)
Write(); //might close
If (timeout expire)
Close(fd);
}
Busy waits!
Need a system call that can block on a Set of fds
Poll(…)
Remote Procedures Call (RPC)
Pixel(int x, int y, int color) {
Marshal argument into a message
Send a message
Wait reply
)
--- Danny