Programming with Ethernet using ScaPy

ScaPy is a Python tool and library for working with network packets. This tutorial is about using it to interact with Ethernet in our own programs. Complete the exercises in the last section.

Table of Content

Determining Version of ScaPy

ScaPy is an evolving software, before we consult with any documentation, we should know the version of ScaPy installed. There are a few methods to determine the version.

  1. Run ScaPy and observe the version number announced:

    $ scapy3
    
  2. Use pip

    $ pip3 list | grep scapy
    
  3. Print __version__ of the scapy package, e.g.,

    $ python -c "import scapy; print(scapy.__version__);"
    

Determinning Version of Python

It is also a good habit to know the version of Python interpreter you are using:

$ python --version

Experiments

We run ScaPy first:

$ scapy3

When we run ScaPy, we in fact run the Python interpreter with ScaPy loaded.

Query Network Interfaces

To list network interfaces in ScaPy, we issue the following function call:

get_if_list()

Below is an example of such an invocation:

>>> get_if_list()
['enp0s8', 'enp0s16', 'lo', 'enp0s10', 'enp0s9', 'enp0s3']
>>>

You may wonder what `get_if_list` is. You can use its online documentation:

```python
>>> help(get_if_list)
Help on function get_if_list in module scapy.arch.linux:

get_if_list()

It tells us that the function belongs to a package called scapy.arch.linux. We can list what this package offers:

dir(scapy.arch.linux)

The list of functions the package offers is self-explanatory, at least to an extent. For instance, if we wish to retrieve an Ethernet interface’s MAC address, we can invoke the following functions, e.g., for interface “enp0s8”:

get_if_hwaddr('enp0s8')

or

get_if_raw_hwaddr('enp0s8')

Building Ethernet Frames

To build an Ethernet frame, we use ScaPy’s Ether class to create an object:

packet = Ether()

To obtain a human-readable representation of such a packet, we can use the repr method:

print(repr(packet))

What can we do with this packet? You can view its documentation by

help(packet)

If you are patient, you will see that the documentation contains:

fields_desc = [<Field (Ether).dst>, <Field (Ether).src>, <Field (Ether...

Hopefully, this will refresh your memory about the Ethernet frame format we discussed. Now, let’s consider the Ethernet called “DiamondMidwoodFlatbush”. There are multiple hosts on the Ethernet. Suppose two hosts are named “midwood” and “flatbush”. Host “midwood” has an interface whose Ethernet address is “08:00:27:08:0d:a1” while host “flatbush” has an interface whose Ethernet address is “08:00:27:cb:67:1d”. For instance, we can look up the hardware addresses of the network interfaces at host “midwood” via the following:

>>> socket.gethostname()
'midwood'
>>> for dev in get_if_list():
...:     print(f"{dev:8s} {get_if_hwaddr(dev):17}")
...:
...:
enp0s8   08:00:27:a1:33:b4
enp0s16  08:00:27:a3:d1:e4
lo       00:00:00:00:00:00
enp0s10  08:00:27:a2:b4:f0
enp0s9   08:00:27:08:0d:a1
enp0s3   08:00:27:5a:ff:ec
>>>

If we want to send a message “Hello, World!” from host “midwood” to host “flatbush”, we shall arrange the Ethernet packet as follows:

>>> packet = Ether()
>>> print(repr(packet))
<Ether  |>
>>> packet.src = "08:00:27:08:0d:a1"
>>> packet.dst = "08:00:27:cb:67:1d"
>>> packet.payload = Raw("Hello, World!")
>>> print(repr(packet))
<Ether  dst=08:00:27:cb:67:1d src=08:00:27:08:0d:a1 |'Hello, World!'>
>>>

Sending Ethernet Frames

Ethernet is a “hardware network” or a host-to-network technology. Loosely speaking, we can regard it as a layer 2 protocol. To do with the Ethernet packet we crafted, we can look up what ScaPy offers us, i.e.,

dir(scapy.layers.l2)

If you are patient, you shall see:

     |  ----------------------------------------------------------------------
     |  Static methods defined here:
     |
     |  send_function = sendp(x, inter=0, loop=0, iface=None, iface_hint=None, count=None, verbose=None, realtime=None, return_packets=False, socket=None, *args, **kargs)
     |      Send packets at layer 2
... ...

Thus, we can send the Ethernet frame using

sendp(packet, iface="enp0s9")

Receiving Ethernet Frames

To receive an Ethernet frame, we simply run sniff to capture frames. Depending on the network, in particular, when the network is very “chatty”, we may have to apply a filter to capture only those that are of our interest.

For instance, we anticipate that the packet we are sending to arrive at interface “enps09” at host “flatbush”, we would run as follows at host “flatbush” before we send the packet at host “midwood”:

packets = sniff(prn=lambda p: p.summary(), iface="enp0s9")

If the network is “chatty”, we may still have a difficult time to know whether the packet we sent arrived. In this case, we can apply a filter:

packets = sniff(prn=lambda p: p.summary(), iface="enp0s9", filter="ether src 08:00:27:08:0d:a1")

If there is still significant amount of packets captured, we can add a condition to the filter narrow down the packets based on the content of the payload:

packets = sniff(prn=lambda p: p.summary(), iface="enp0s9", filter="ether src 08:00:27:08:0d:a1 && ether[14:2] = 0x4865")

Examining Ethernet Frames

We can examine the crafted or the received Ethernet frames in a variety of ways. Here are a few examples:

>>> type(packets)
scapy.plist.PacketList
>>> len(packets)
1
>>> print(repr(packets[0]))
<Ether  dst=08:00:27:cb:67:1d src=08:00:27:08:0d:a1 type=0x9000 |<Raw  load='Hello, World!' |>>
>>> print(packets[0].summary())
08:00:27:08:0d:a1 > 08:00:27:cb:67:1d (0x9000) / Raw
>>> raw(packets[0])
b"\x08\x00'\xcbg\x1d\x08\x00'\x08\r\xa1\x90\x00Hello, World!"
>>> hexdump(packets[0])
0000  08 00 27 CB 67 1D 08 00 27 08 0D A1 90 00 48 65  ..'.g...'.....He
0010  6C 6C 6F 2C 20 57 6F 72 6C 64 21                 llo, World!
>>> packets[0].show()
###[ Ethernet ]###
  dst= 08:00:27:cb:67:1d
  src= 08:00:27:08:0d:a1
  type= 0x9000
###[ Raw ]###
     load= 'Hello, World!'
>>> 

Python Program using ScaPy

The sending logic can be put in a single Python program, as shown below where we name the sending program as “scapy_ether_send.py”:

$ cat scapy_ether_send.py
import scapy.layers
import scapy.layers.l2


def main():
  intf = "enp0s9"

  msg = input("Enter a message to send: ")
  packet = scapy.layers.l2.Ether()
  packet.src = "08:00:27:08:0d:a1"
  packet.dst = "08:00:27:cb:67:1d"
  packet.payload = scapy.packet.Raw(msg)
  scapy.layers.l2.sendp(packet, iface=intf)

if __name__ == "__main__":
  main()

The receiving logic can also be in a Python program, as shown below where we name the program as “scapy_ether_recv.py”:

import signal
import sys
import scapy.all as scapy

def signal_handler(sig, frame):
    print('You pressed Ctrl+C!')
    sys.exit(0)

def main():
  signal.signal(signal.SIGINT, signal_handler)
  while True:
    print("Waiting for message to arrive.")
    packets = scapy.sniff(
            iface="enp0s9",
            filter="ether src 08:00:27:08:0d:a1",
            count=1
    )
    src = packets[0].src
    msg = packets[0].payload.load.decode("utf-8")
    print(f"Received from {src}: {msg}")

if __name__ == "__main__":
    main()

Exercises and Questions

  1. We use Oracle VirtualBox to create an experiment environment. How do we know two hosts are on the same Ethernet? In another word, if we wish to place to two or more hosts on the same Ethernet, how do we configure in Oracle VirtualBox?
  2. In the above, we demonstrate the example of a filter to limit the packets captured as follows:

    packets = sniff(
       prn=lambda p: p.summary(),
       iface="enp0s9",
       filter="ether src 08:00:27:08:0d:a1 && ether[14:2] = 0x4865")
    

    Explain the meaning of the values, such as 14, 2, 0x4865 in ether[14:2]?

  3. For the programs listed here, we must run the programs as the root user. Why do you think we are required to run the program as the root user?
  4. Using the provided program as example, design a program to broadcast messages to all hosts on an Ethernet.
  5. Design an experiment to test your program. Describe the experiment setup and results. In your setup, you need to show that broadcasting works.