We previously wrote a brief introduction to networking, however networking is a larger topic than that, so we've decided to produce a larger series to try and do the subject justice.

Binding services to sockets

We're going to illustrate binding this service from Python since shell is not appropriate, and C is too verbose.

However we're going to use the low-level Python bindings rather than the socketserver class, to show how it works beneath the layers of abstraction.

As with some previous articles, we are going to describe how it works in the form of a script with commentary. You can download the full script here.

#!/usr/bin/python
#chuckle.py

Python's bindings are in the socket module, we're doing a qualified import to resemble the API from C.

from socket import socket, AF_INET, SOCK_STREAM

There's lots of setup stages involved in making a connection available, following the general builder pattern, rather than having a single syscall that takes a huge number of options, as it is more extensible.

The socket() syscall takes the parameters family, socket type and protocol. This describes exactly what kind of socket to create. This will be explained in greater detail later, but here we create a socket with an IP address which acts as a stream.

s = socket(AF_INET, SOCK_STREAM, 0)

One side of the connection needs to bind to a port, so that the other can connect to it. The address specifies which address your machine has, that the service should be served on. '0.0.0.0' means "all my addresses", but we're going to use '127.0.0.1' which means "only my internal address" so that we can't have anything on the network connect to it:

addr = '127.0.0.1'

A port number also needs to be chosen to provide the service. The address denotes how to find the service, while the port represents which of the potential services available should be connected to.

port = 12345
s.bind((addr, port))

The call to listen() is further configuration, of how many connections to buffer before refusing new connections.

s.listen(0)
while True:

The accept() call blocks until there are available connections to service and returns some information about where the connection was from.

    conn, hostaddr = s.accept()

The python connection object wraps a file descriptor that can be used as any normal file, so we do some conversion so we can use it as one.

    f = conn.makefile('rw')
    while True:
        f.write('> ')
        f.flush()
        line = f.readline()
        if not line:
            break
        if line == 'to me\n':
            print('to you')
        elif line == 'oh dear\n':
            print('oh dear oh dear')
            break
    conn.close()

We can demonstrate that this works with the netcat command.

$ python chuckle.py &
$ nc 127.0.0.1 12345
> to me
to you
> oh dear
oh dear oh dear
^C
$

We can make this service start on boot by creating and enabling a systemd unit like this:

$ sudo install -D -m755 chuckle.py /usr/local/libexec/chuckle.py
$ sudo tee /etc/systemd/system/chuckle.service >/dev/null  <<'EOF'
[Unit]
Description=Chuckle server
[Service]
ExecStart=/usr/local/libexec/chuckle.py
[Install]
WantedBy=multi-user.target
EOF
$ sudo systemctl daemon-reload
$ sudo systemctl enable chuckle.service

Connecting to services

We glossed over how connecting works and just used netcat, so let's see what's involved by writing our own program. You can download this script here.

#!/usr/bin/python
#connect.py
import sys

As before we need the same socket definitions to make a socket.

from socket import socket, AF_INET, SOCK_STREAM
s = socket(AF_INET, SOCK_STREAM, 0)

Now instead of binding and listening we call connect().

s.connect(('127.0.0.1', 12345))

The rest is just copying input from your terminal to the socket. Note that this is unidirectional and line based, while netcat is bidirectional and asynchronous.

f = s.makefile('w')
while True:
    line = sys.stdin.readline()
    if not line:
        break
    f.write(line)
    f.flush()

It is simpler to create connections than bind services, but the address conventions are the same.

Our existing service should still be running, so we can connect to it just like we did before with netcat.

$ python connect.py
> to me
to you
> oh dear
oh dear oh dear
^C
$