16.3 Tips on Writing Network Programs
If you are coding a new network
service, there are also a number of pitfalls you will need to
consider.
16.3.1 Things to Do
Do a reverse
lookup on connections when you need a hostname for any reason. After
you have obtained a hostname to go with the IP address you have, do
another lookup on that hostname to ensure that its IP address matches
what you have.
Include some form of load shedding or load limiting in
your server to handle cases of excessive load. Consider what should
happen if someone makes a concerted effort to direct a denial of
service attack against your server. For example, you may wish to have
a server stop processing incoming requests if the load goes over some
predefined value.
Put reasonable timeouts on each network-oriented read
request. A remote server that does not respond quickly may be common,
but one that does not respond for days may hang up your code awaiting
a reply. This rule is especially important in TCP-based servers that
may continue to attempt delivery indefinitely.
Put reasonable timeouts on each network write
request. If some remote server accepts the first few bytes and then
blocks indefinitely, you do not want it to lock up your code awaiting
completion.
Make no assumptions about the content of input data, no matter what
the source is. For instance, do not assume that input is
null-terminated, contains linefeeds, or is even in standard ASCII
format. Your program should behave in a defined manner if it receives
random binary data as well as expected input. This is especially
critical on systems that support locales and that may get
Unicode-formatted input. When checking the content of input, try to validate it against
acceptable values, and reject anything that doesn't
match what's allowed. The alternative (and all too
common) strategy of rejecting invalid values and
allowing anything else requires you to specify (and in some cases,
predict) all of the possible invalid values that might arise, ever.
Make no assumptions about the amount of input sent by the remote
machine. Put in bounds checking on individual items read, and on the
total amount of data read.
Consider doing a call to the
authd service on the remote site to identify
the putative source of the connection. However, remember not to place
too much trust in the response, and to build in a timeout in the
event that you don't get an answer.
Consider adding some form of session encryption to prevent
eavesdropping and to foil session hijacking. But
don't try writing your own cryptography functions;
see Chapter 7 for algorithms that are known to be
strong. Using SSL builds on known technology and may speed your
development (and reduce the chance of new programming errors).
Build in support to use a proxy. Consider using the
SOCKS program to ensure that the code is firewall-friendly.
Make sure that good logging is performed. This includes logging
connections, disconnects, rejected connections, detected errors, and
format problems.
Build in a graceful shutdown so that the system operator can signal
the program to shut down and clean up sensitive materials. Usually,
this process means trapping the TERM signal and cleaning up
afterwards.
Consider programming a "heartbeat"
log function in servers that can be enabled dynamically. This
function will periodically log a message indicating that the server
is still active and working correctly, and possibly record some
cumulative activity statistics.
Build in some self recognition or locking to prevent more than one
copy of a server from running at a time. Sometimes services are
accidentally restarted; such restarts may lead to race conditions and
possibly the destruction of logs if the services are not recognized
and are stopped early.
16.3.2 Things to Avoid
Don't write a new protocol. It's
not easy to write a good network protocol, especially one that
provides adequate security for authentication and authorization. Just
as most cryptosystems devised by non-cryptographers are weak, most
network protocols devised without expert consultation are flawed.
Before you set out to write a new network protocol, see if a
tried-and-true protocol already exists that can serve your needs.
If you must write a new protocol, don't write an
asymmetric protocol. In an asymmetric protocol, a small client
request results in a large server response. These kinds of protocols
can make it easy to perform denial of service attacks on the server.
This is of particular concern with connectionless services. Instead,
write a protocol in which the amount of data exchanged is roughly
equal on each side, or where the client is forced to do more work
than the server.
Don't make any hard-coded assumptions about service
port numbers. Use the library getservbyname(
) and related calls, plus system include files, to get
important values. Remember that sometimes constants
aren't constant.
Don't place undue reliance on the fact that any
incoming packets are from (or claim to be from) a low-numbered,
privileged port. Any PC can send from those ports, and forged packets
can claim to be from any port.
Don't place undue reliance on the source IP address
in the packets of connections you received. Such items may be forged
or altered.
Don't require the user to send a reusable
password
in cleartext over the network connection to authenticate himself. Use
either one-time passwords, or some shared, secret method of
authentication that does not require sending compromisable
information across the network. Consider using this approach: The APOP protocol used in the POP mail
service has the server send the client a unique character string,
usually including the current date and time. The client then hashes the timestamp together with the
user's password. The result is sent back to the
server. The server also has the password and performs the same
operation to determine if there is a match.
The password is never transmitted across the network. This approach
is described further in the discussion of POP in Chapter 12.
|