|
Page 1 of 4
In part one, we presented a real-world story where a Linux server – or daemon – solved a need for an ISP. The code to achieve this was introduced, and annotated. However, we left the best till now – the actual socket handling itself, plus the rc.d script to start the daemon at boot time and kill it at shutdown.
Socket handling
This daemon, like any other, is designed to run in the background, and respond to network connections over TCP/IP. We can achieve this using the best known TCP/IP handling interface, Berkeley sockets. Our main() routine in dwserv.cpp sets it all up.
int main (int argc, char *argv [])
{
struct sockaddr_in sin, fsin;
struct protoent *ppe;
int sock, ssock;
int alen;
int port = 5000;
int qlen = 5;
int pid;
int fd;
char nowtime [26];
char Remote [80];
int connections = 0;
FILE *logfp = NULL;
First, as we are going to be creating accounts, the server must be run as the super-user. However, this need not cause any fear, because our server is both robust and secure. And, of course, it is open source and can thus be inspected and enhanced.
if (geteuid () != 0)
{
printf ("\n%s must be run with super-user privileges.\n", argv [0]);
return 0;
}
We then establish a server socket, bound to the port we have chosen to use, as specified by the port variable above.
// Set up the socket.
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons (port);
if ((ppe = getprotobyname ("tcp")) == 0)
errexit ("Can't find tcp: %s\n", strerror (errno));
if ((sock = socket (PF_INET, SOCK_STREAM, ppe->p_proto)) < 0)
errexit ("Can't create socket: %s\n", strerror (errno));
if (bind (sock, (struct sockaddr *) &sin, sizeof (sin)) < 0)
errexit ("Can't bind to port: %s\n", strerror (errno));
if (listen (sock, qlen) < 0)
errexit ("Can't listen on port: %s\n", strerror (errno));
We set the server to run in the background by using the fork system call to create a second process, and by disconnecting the new process from the controlling tty. The original process is terminated.
if ((pid = fork ()) < 0)
errexit ("Error setting up server: %s\n", strerror (errno));
if (pid) // Non-zero is parent.
{
printf ("Server pid is %d.\n", pid);
if (strlen (FileName) > 0)
log ("Server pid is %d.\n\n", pid);
exit (0);
}
fd = open ("/dev/tty", O_RDWR);
ioctl (fd, TIOCNOTTY, 0);
close (fd);
The server now simply sits passively in the background listening forever for network connections.
If an incoming request is made, then a new, slave, socket is created to process it. This is hived off into a child process. This permits the server socket and process to continue accepting more connections because it has offloaded the processing. This gives the appearance of allowing multiple simultaneous connections.
while (1)
{
alen = sizeof (fsin);
ssock = accept (sock, (struct sockaddr *) &fsin, &alen);
if (ssock < 0)
{
if (errno == EINTR)
continue;
else
errexit ("accept: %s\n", strerror (errno));
}
strcpy (Remote, IPtoAddress (fsin.sin_addr));
log ("%s: connect from %s\n", CurrentDateTime (nowtime), Remote);
signal (SIGCHLD, reaper);
connections++;
if (fork () == 0)
{
process (ssock, Remote, connections);
exit (0);
}
else
close (ssock);
}
}
The process method, called above, deciphers the command received from the client and calls the appropriate method to deal with it - such as CreateAccount. Any error messages are returned to the client through the same socket.
|