Chapter 2 ■ UDp
24
Note that the client is vulnerable to anyone who can address a UDP packet to it. This is not an instance where a
man-in-the-middle attacker has control of the network and can forge packets from false addresses, a situation that can
be protected against only by using encryption (see Chapter 6). Rather, an unprivileged sender operating completely
within the rules and sending a packet with a legitimate return address nevertheless has its data accepted.
A listening network client that will accept or record every single packet that it sees, without regard for whether the
packet is correctly addressed, is known technically as a promiscuous client. Sometimes we write these deliberately,
as when we are doing network monitoring and want to see all of the packets arriving at an interface. In this case,
however, promiscuity is a problem.
Only good, well-written encryption should really convince your code that it has talked to the right server. Short of
that, there are two quick checks you can do. First, design or use protocols that include a unique identifier or request
ID in the request that gets repeated in the reply. If the reply contains the ID you are looking for, then—so long as
the range of IDs is large enough that someone could not simply be quickly flooding you with thousands or millions
of packets containing every possible ID—someone who saw your request must at least have composed it. Second,
either check the address of the reply packet against the address that you sent it to (remember that tuples in Python
can simply be == compared) or use connect() to forbid other addresses from sending you packets. See the following
sections “Connecting UDP Sockets” and “Request IDs” for more details.
Unreliability, Backoff, Blocking, and Timeouts
Because the client and server in the previous sections were both running on the same machine and talking through its
loopback interface—which is not a physical network card that could experience a signaling glitch—there was no real
way that packets could get lost, and so you did not actually see any of the inconvenience of UDP in Listing 2-1. How
does code become more complicated when packets can really be lost?
Take a look at Listing 2-2. Instead of always answering client requests, this server randomly chooses to answer
only half of the requests coming in from clients, which will let you see how to build reliability into your client code
without waiting what might be hours for a real dropped packet to occur on your network!
Listing 2-2. UDP Server and Client on Different Machines
#!/usr/bin/env python3
Foundations of Python Network Programming, Third Edition
https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter02/udp_remote.py
UDP client and server for talking over the network
import argparse, random, socket, sys
MAX_BYTES = 65535
def server(interface, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((interface, port))
print('Listening at', sock.getsockname())
while True:
data, address = sock.recvfrom(MAX_BYTES)
if random.random() < 0.5:
print('Pretending to drop packet from {}'.format(address))
continue
text = data.decode('ascii')
print('The client at {} says {!r}'.format(address, text))
message = 'Your data was {} bytes long'.format(len(data))
sock.sendto(message.encode('ascii'), address)