Linux Format 26 [[ Typographical notes: indented text is program listing text surrounded in _underscores_ is italicised/emphasised ]] Perl Tutorial TITLE: Fun with servers Last month we took a brisk -- and highly selective -- tour of perl inter-process communications (including signals, pipes, and sockets). At the end we left off with a very simple (indeed, brain dead) example of a client and a server, written using the IO::Socket module, which conceals most of the heavy lifting behind a halfway usable interface. (Note: if you want to understand the heavy lifting involved in socket programming, you've come to the wrong tutorial. You want to start by reading "Advanced Programming in the UNIX Environment" by W. Richard Stevens. This tutorial is about doing it the easy way, and is not the right place to start if you want to write a high-performance web server to knock Apache off its throne.) Let's recap quickly. A client/server setup is one where one machine or program (the client) sends a message (possibly a chunk of data for munging, possibly a set of keystroke events, possibly a HTTP request for a web page) to another machine or program (the server). The server processes the message or data and sends something back. Why do I say "machine or program"? Because it's possible for a client program to run on the same machine as the server; for example, the MySQL query monitor, which lets you interactively type SQL commands to a MySQL database, often runs on the same machine as the database server program. Linux provides lots of inter-process communication (IPC) mechanisms for transferring data between a client and a server program, but one main mechanism, the socket, is used for transfers across a network. (Note that both ends of such a transfer may reside on the same machine -- or not. It gets confusing, doesn't it?) Sockets resemble bidirectional pipes, and Perl treats sockets roughly like a file handle that is open for reading and writing simultaneously. Here's a really simple server that we introduced last week. It listens for connections to the local machine on port 999, and when one arrives, sends the current date and time to the client (by printing it on the socket connection): use IO::Socket::INET; my $port = 9999; # some unused TCP port to bind to $server = IO::Socket::INET->new(LocalPort => $port, Type => SOCK_STREAM, Reuse => 1, Listen => 10) or die "Couldn't bind to port $port: $!\n"; while ($client = $server->accept()) { $client->print(scalar(localtime(time)), "\n"); } What does all this mean? The first point to note is that an IO::Socket::INET object is not a raw low-level UNIX socket; it's a Perl object that encapsulates a socket. Now would be a good time to look at the documentation for IO::Handle and IO::Socket, both of which are applicable (type "perldoc IO::Handle" and "perldoc IO::Socket"). An IO::Handle takes a file handle and wraps an object around it, supplying methods; IO::Socket provides methods for binding the filehandle to a socket and sending/receiving data, and IO::Socket::INET provides facilities for internet domain sockets. (Other domains exist for sockets communicating over AppleTalk, AX.25, other network protocols, or the UNIX domain -- inter-process communication on the local host). The next thing to note is that, as with all TCP/IP communications, connections are characterised by an IP address and a port number between 1 and 65535. In this case, the new() method is supplied with a port to _listen_ to. Any connections to the local machine on that port (in this case, 9999) will go to the IO::Socket::INET object we've defined. The "Listen" parameter tells IO::Socket::INET to set up a listen queue with ten slots for incoming connections -- any more simultaneous connections will be silently lost -- and to be prepared to Reuse the socket address. The "Type" parameter is a constant used by the kernel to identify the socket type in use -- in this case a stream of packets rather than a datagram connection. Having called new() (which in turn calls the low-level system call bind()), we call accept(). This tells the socket that it's to wait for connections and, when one arrives, to return a copy of the IO::Socket object that can be used for communication with the client that just connected. You communicate by reading or writing; in this case, when we accept the client connection we simply print the current time to it. So what's missing here? Why can't we ellaborate on this and turn it into an all-singing, all-dancing server? For starters, there's a hard limit in the listen queue; if more than ten connections come in, this mini-server will simply dump them. But that's trivial compared to the more serious objection -- that this solution is unidirectional. Almost every such communication is bidirectional; either the client sends some data and the server processes it and returns it, or vice versa. But both of them want to talk over the same socket. How do they know when to read and when to write? The usual solution is to use a protocol -- a well-defined set of rules governing how a client and server should communicate. The purpose of a protocol is to avoid deadlock -- that is, both the client and the server waiting to read or write a reply at the same time (so that neither of them ever write or read the other end of the connection, leaving the session hanging). By way of example, SMTP (simple mail transport protocol, defined by RFC821, which you can read at http://www.ietf.org/rfc/rfc0821.txt) is a protocol. The client connects, and initiates the session by sending a HELO message (the string HELO, followed by whitespace, the sender's domain, then a carriage return and line feed). The server is supposed to reply with a three-digit numeric code, optional whitespace, some text, and a carriage-return-line-feed line ending. (Code 220 means 'service ready'; code 421 means 'service not available', and so on.) After initiating a connection with HELO, the client should read the response from the server and either close the connection (if it's not active) or begin sending mail. It does so by issuing a MAIL FROM command, then a RCPT TO command, then a DATA command -- then printing the body of the message, indicating end of transmission by sending a line with a period on its own. At each stage, the server replies with a status code indicating whether the action was successful or not. If you're writing an SMTP client you can't take success for granted -- your program should be able to abort gracefully if, for example, you send message data, write the final line, and the server replies 451 (requested action aborted) or 452 (insufficient storage space on server) or something else. As you can see, there's more to sending a message than a simple loop like: while ($client = $server->accept()) { $client->print("HELO FROM ", $my_hostname, "\r\n"); $client->print("MAIL FROM ", $sender, "\r\n"); $client->print("RCPT TO ", $recipient, "\r\n"); $client->print("DATA\r\n"); $client->print( join("\r\n", @message)); $client->print(".\r\n"); $client->print("QUIT\r\n"); } While this _might_ work some of the time, if the MAIL FROM fails for some reason then the rest of the session is junk -- worse, it may get into a confusing deadlock with the server, or end up doing something wholly unexpected and unwelcome (for example, if one of the lines in the message @message begins with the word "turn" while the SMTP server still thinks its talking to a sane client). The converse holds true at the server end of the connection. Never assume that you're talking to a sane peer or that every action they (or you) request is possible! Another point we haven't mentioned yet is that client/server dialogues take time. If another client starts banging on the door while our server is busy digesting a large email from the first, we are going to be stuck waiting for the first session to terminate. For this reason, we normally write servers to be multi-threaded, or to fork children, to handle each dialogue with a client. When a connection is returned by accept() we call fork(); the parent goes back to waiting for another connection to come in, while the child talks to the client. SUBHEADING: How to write a server As a way of getting to grips with the problem, let's write a really simple TCP/IP server daemon. The major internet protocols are a bit hairy (or at least, too big to examine in detail in a four-page tutorial). So we're going to invent our very own pocket protocol, for which we can later write a client. By way of example, think about the humble manual page. When you type "man bash" to read the bash manual, the program "man" goes forth and looks for the file bash.1, stashed in /usr/man/man1 (or wherever /etc/man.conf says it lives), feeds it to the nroff formatter and various other filters (if necessary), and pipes it to your pager so you can read it on-screen one page at a time. The standard man implementation on Linux has a load of other features; it can cache nroff output files to save work, search in multiple manual sections, and so on. If you've got a network of machines, you may not want to install the man pages on all of them. Wouldn't it be useful to keep all the man pages on a single server, and use a tool called "man" that instead of looking for a local file could send a request to the man page server over the network, and display whatever it sent back? As it happens, it isn't hard to write a server like this (although there are security issues involved in it). So we're going to write a server, and a man-server client. SUBHEADING: Designing the protocol The first step is to define the protocol our client and server talk. We're not going to clone the existing "man" program's functionality in the server -- instead, the server will run "man" on a local tree of man pages, and accept whatever man delivers. So all our protocol needs to do is to deliver the equivalent of a "man" command line to the server, read the server's reply, drop the connection, and display the reply in your pager (as defined in your shell environment variable $PAGER -- it's usually the utility "less"). We aren't going to simply have the server execute whatever command the client sends us; that would open up a screaming security hole by allowing strangers to execute arbitrary commands on the server. Nor are we going to implement some of the man command's more obscure features (such as dumping a man page in typeset form to a printer). Instead, we're going to provide the following options: -k Search the short descriptions and man page names for the regular expression . Print out any matches. -f Print the short descriptions of any man pages named -a Print all the man pages named (from each manual section, in turn) Print the man page from section -- for example "man 5 hosts" prints the man page "hosts.5" from section 5 of the manual, found in /usr/man/man5 Print the man page named in the first section we find A manpage protocol session is going to be pretty simple. The client opens a connection to the server on some standard port (I'm going to pick 8888, for no very good reason) and sends a request. The server waits for the request to terminate, then sends a reply and closes the connection. If the request isn't terminated or the connection hangs, the server should close the connection anyway after 60 seconds. A request looks like this: PAGE SECTION END Each line ends with a newline, and begins with a keyword. In this case, PAGE means "the following string is the name of a man page to return". SECTION introduces a man page section. So, to get the man page for bash, the client must open a connection and send: PAGE bash SECTION 1 END The can be a number (1-8) or the keyword ALL, meaning send all the man pages named -- see below for how they're delivered. Two other requests can replace the PAGE/SECTION pair: SEARCH END (Which is equivalent to man -k ) And: DESCRIBE END (Which is equivalent to man -f ). The server waits until it sees an END line, and then parses the request. If it's confused, the server sends a status message and closes the connection. If it understands the request, the server sends a status message and then the output from the man command, before closing the connection. We only need two status commands right now: 200 - OK 500 - Bad Request Each status line is terminated by a newline, then the server prints the response. A response consists of a bunch of newline-terminated lines containing the output from man. In the case of SECTION ALL, it consists of the output from man for each man page with the given name, glued end to end with no delimiters (just as man -a outputs it). SUBHEADING: Making the server work We cheat here and use NetServer::Generic (see boxout, "Using NetServer::Generic") to write our server. NetServer::Generic is designed specifically to make this sort of simple line-based protocol easy to write. We have to do some basic setup, then write a single command, the callback executed when a connection comes in. It reads from the client on STDIN, and writes its output to STDOUT. our manserver starts out looking like this: #!/usr/bin/perl use NetServer::Generic; $main::manprog = "/usr/bin/man"; # Simple man page server sub make_man_request { } sub execute_man_request { } my ($cb) = sub { print STDOUT "500 - bad request\n"; return; }; my (%config) = ("port" => 8888, "callback" => $cb); my ($foo) = new NetServer::Generic(%config); print "$0 [$$] started on port 8888\n"; $foo->run(); In NetServer::Generic, the module does all the heavy lifting of listening for connections and managing children. When a connection from a client arrives, it spawns a child to deal with it -- when calls a subroutine called a "callback" -- in this case, the closure $cb. (A closure is just an anonymous subroutine with its own local variable scope -- it lets us pass subroutines around as if they're variables, and stash objects away inside them for later reuse.) The subroutine callback $cb is where the work happens; right now all it does is print "500 - bad request" on Standard Output, and exit. (In NetServer::Generic, anything the client sends to the server shows up on Standard Input, and to send a message back to the client you simply print to Standard Output.) There are a couple of other skeletal items here: make_man_request() and execute_man_request(). These are utility subroutines called by $cb. make_man_request() takes whatever parameters are passed in the request and turn them into a command line for the "man" program (named in $main::manprog). execute_man_request() takes the command line and executes it, then returns the results to $cb. $cb's job is to communicate with the client -- to read request lines and accumulate a request, then execute it, and return the result. Along the way it returns 500 errors if it sees anything unexpected, or if either make_man_request() or execute_man_request() return undef (as they do if something goes wrong). One point to note when looking at the source of $cb is that it's rather gnarly; there's an eval block wrapped around most of it to trap a SIGALRM signal that we set to 60 seconds -- this is to avoid hung connections. (See Linux Format 25 for a discussion of signal handling in Perl, and the use of eval blocks in this context.) Inside the eval block, the main job of the code is to read lines from STDIN and be rude to the client if what its sending doesn't resemble a well-formed manserver transaction. As requests come in, they're split up into tokens and the first token is identified as a valid protocol tag. This is used as the key in a hash, and all the parameters to it are saved as an anonymous array hanging off the hash key. (In practice we only ever use element 0 of this array, but it's there in principle if we decide to extend the protocol.) When the main loop in $cmd has read a line beginning with END, it calls make_man_request() -- this checks the contents of our hash of request tags for validity and throws errors if they're malformed. If they make sense it assembles a line to pass to the man command, and returns this to $cb. $cb then passes this to execute_man_request(), which again provides an eval block wrapper around the external command -- in case man hangs for some reason. What it returns is either undef (an error) or a reference to an array of lines spat out by man. This is then passed to the client, and $cb closes the connection and implicitly exits. There are a few other things to note about this use of NetServer::Generic. The first is that this server will only run on localhost at present -- because we don't tell it to bind to a specific IP address or hostname, it defaults to localhost. Next month we'll do something about this, making it accessible to a local network. A second issue is a bit more subtle. NetServer::Generic is robust and stable but not foolproof. Because it reads lines delimited by newline characters from a socket, it is vulnerable to denial of service attacks by malicious users. The timeout mechanism prevents a malicious user spawning lots of connections and filling up the server's process table, but the use of line- oriented input means that an attacker with a broadband connection who can ram garbage down the line fast enough can make the server soak up lots of memory. One way round this is to read from the socket in raw mode; we'll look into this later, too, along with other ways of making your servers bulletproof. Finally, in next month's tutorial we'll look at how to write a client that talks to this server. In the meantime, if you run it on localhost you can type: telnet localhost 8888 And talk to it -- just type manserver protocol commands and it will answer! PAGE bash SECTION ALL END or DESCRIBE bash END Have fun! END (BODY TEXT) BOXOUT: Using NetServer::Generic NetServer::Generic is a Perl module for writing TCP/IP servers; you can install it from CPAN (the Combined Perl Archive Network) by typing at the command line: perl -MCPAN -e 'install NetServer::Generic'; (Note: NetServer::Generic relies on the module Time::HiRes, a high-resolution timer -- this will be installed automatically if you use the CPAN command above.) I wrote NetServer::Generic because I was writing client/server applications in Perl at work, and got annoyed with having to write several hundred lines of code to handle managing child processes, socket connections, and timeouts. NetServer::Generic's goal is to allow the programmer to focus on the important parts of a server, the subroutine that handles incoming data and does something with it. To write a NetServer::Generic application, you first decide what sort of server you want. NetServer::Generic supports the standard UNIX forking model (where a child is forked to handle each connection). It can also run in pre-forked mode, similar to Apache. (It has somewhat less successful support for a non-forking mode -- this works fine on lightly- loaded systems but can block waiting for i/o on heavily loaded machines. And it also provides a 'client' mode, in which it issues a bunch of requests to a different server -- this is supplied so that you can write tools to test another server program by bombarding it with requests.) You also need to specify which TCP port you want to listen on, whether to bind to a hostname or IP address (other than localhost), and (optionally) a list of hosts/networks/patterns to refuse access to, or a list of hosts/networks that you're willing to accept connections from. Then you throw all this data at a new NetServer::Generic object, which does the rest. For example: my $server_cb = sub { # subroutine that reads from STDIN and writes to STDOUT goes # here } my ($foo) = new NetServer::Generic; $foo->port(9000); $foo->callback($server_cb); $foo->mode("forking"); print "About to start server\n"; $foo->run(); Full documentation can be obtained by typing "perldoc NetServer::Generic" once you've installed it. END BOXOUT