This excerpt from "Learning Python Network Programming" looks at how Python lets us interface with the network stack.
The power of the layer model of network protocols is that a higher layer can easily build on the services provided by the lower layers and this enables them to add new services to the network. Python provides modules for interfacing with protocols at different levels in the network stack, and modules that support higher-layer protocols follow the aforementioned principle by using the interfaces supplied by the lower level protocols. How can we visualize this?
Well, sometimes a good way to see inside something like this is by breaking it. So, let's break Python's network stack. Or, more specifically, let's generate a traceback.
Yes, this means that the first piece of Python that we're going to write is going to generate an exception. But, it will be a good exception. We'll learn from it. So, fire up your Python shell and run the following command:
>>> import smtplib
>>> smtplib.SMTP('127.0.0.1', port=66000)
What are we doing here? We are importing smtplib, which is Python's standard library for working with the SMTP protocol. SMTP is an application layer protocol, which is used for sending e-mails. We will then try to open an SMTP connection by instantiating an SMTP object. We want the connection to fail and that is why we've specified the port number 66000, which is an invalid port. We will specify the local host for the connection, as this will cause it to fail quickly, rather than make it wait for a network timeout.
On running the preceding command, you should get the following traceback:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.4/smtplib.py", line 242, in __init__
(code, msg) = self.connect(host, port)
File "/usr/lib/python3.4/smtplib.py", line 321, in connect
self.sock = self._get_socket(host, port, self.timeout)
File "/usr/lib/python3.4/smtplib.py", line 292, in _get_socket
File "/usr/lib/python3.4/socket.py", line 509, in create_connection
File "/usr/lib/python3.4/socket.py", line 500, in create_connection
ConnectionRefusedError: [Errno 111] Connection refused
This was generated by using Python 3.4.1 on a Debian 7 machine. The final error message will be slightly different from this if you run this on Windows, but the stack trace will remain the same.
Inspecting it will reveal how the Python network modules act as a stack. We can see that the call stack starts in smtplib.py, and then as we go down, it moves into socket.py. The socket module is Python's standard interface for the transport layer, and it provides the functions for interacting with TCP and UDP as well as for looking up hostnames through DNS.
From the preceding program, it's clear that the smtplib module calls into the socket module. The application layer protocol has employed a transport layer protocol (which in this case is TCP).
Right at the bottom of the traceback, we can see the exception itself and the Errno 111. This is an error message from the operating system. You can verify this by going through /usr/include/asm-generic/errno.h (asm/errno.h on some systems) for the error message number 111 (on Windows the error will be a WinError, so you can see that it has clearly been generated by the OS). From this error message we can see that the socket module is calling down yet again and asking the operating system to manage the TCP connection for it.
Python's network modules are working as the protocol stack designers intended them to. They call on the lower levels in the stack to employ their services to perform the network tasks. We can work by using simple calls made to the application layer protocol, which in this case is SMTP, without having to worry about the underlying network layers. This is network encapsulation in action, and we want to make as much use of this as we can in our applications.