In this article, you will learn how to communicate over Ethernet using raw sockets and try to send/receive data.

Environment

  • Linux (this is mandatory for using raw sockets)
  • Python 3.6 or higher

Step-by-Step Instruction

Here is a sample code.

Open the terminal and type:

$ git clone https://github.com/y-sira/pyng.git

You can find network interface names and corresponding MAC addresses on your machine using ip command as follows:

$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 08:00:27:dd:d7:43 brd ff:ff:ff:ff:ff:ff
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 08:00:27:b0:d6:ff brd ff:ff:ff:ff:ff:ff
4: enp0s9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 08:00:27:e6:4d:39 brd ff:ff:ff:ff:ff:ff
5: enp0s10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 08:00:27:8e:75:44 brd ff:ff:ff:ff:ff:ff

Move to pyng directory and run the server script:

$ cd /path/to/pyng
$ sudo ./pyngd $SERVER_INTERFACE_NAME

Open another terminal, move to pyng directory, and run the client script to send the frame to the server:

$ cd /path/to/pyng
$ sudo ./pyng $DESTINATION_MAC_ADDRESS $CLIENT_INTERFACE_NAME
Sent!

Make sure that the data client script sent is appeared in the server side terminal.

Raw Socket Programming

Socket APIs

The socket is a collection of APIs for interprocess communication and network communication from applications. It is available on UNIX and Linux. In particular, for Linux, it is easy to handle the data link layer by using socket APIs.

In order to send / receive data using socket APIs, we have to generate a socket descriptor. The socket descriptor can be generated using int socket (int family, int type, int protocol) defined in sys/socket.h. The first argument family specifies the address family. For the data link layer, specify AF_PACKET defined in sys/socket.h. The second argument type specifies the socket type. For the data link layer, specify SOCK_RAW defined in sys/socket.h. The third argument protocol specifies the protocol. Specify ETH_P_ALL to retrieve any Ethernet frames, and specify ETH_P_IP to retrieve Ethernet frames containing a IP packet. The protocol types of the Ethernet frame is defined in linux/if_ether.h.

In Python, there is a standard library that wraps the OS level socket APIs, so we can use it to create a data link raw socket. Please be careful to close() after using the socket.

import socket
ETH_P_ALL = 3
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
s.close()

We use bind() to bind the network interface to the socket. You can check the network interface available on your computer with the following command.

$ ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 08:00:27:dd:d7:43 brd ff:ff:ff:ff:ff:ff
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 08:00:27:b0:d6:ff brd ff:ff:ff:ff:ff:ff
4: enp0s9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 08:00:27:e6:4d:39 brd ff:ff:ff:ff:ff:ff
5: enp0s10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    link/ether 08:00:27:8e:75:44 brd ff:ff:ff:ff:ff:ff

For example, to bind the network interface enp0s3 to the socket, write as follows.

import socket
ETH_P_ALL = 3
interface = 'enp0s3'
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
s.bind((interface, 0))
# do something
s.close()

Now you are ready to send and receive raw data on the data link layer using the network interface enp0s3.

Receive Data Using Raw Sockets

Use recv() to receive data using a socket. Specify the buffer size in first argument of recv(). recv() will block until ready to receive data from the socket. The return value of recv() is the received byte sequence. This byte string consists of the header and the payload of the Ethernet frame.

When receiving data sent to enp0s3, the script server.py is as follows:

import socket
ETH_P_ALL = 3
ETH_FRAME_LEN = 1514  # Max. octets in frame sans FCS
interface = 'enp0s3'
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
s.bind((interface, 0))
data = s.recv(ETH_FRAME_LEN)
print(data)           # => b'\x08\x00\x27\xdd\xd7\x43\x08\x00\x27\x8e\x75\x44\x88\xb5Hi'
s.close()

Note that the data link raw sockets require a root permission. Please run the above script as root.

When the data is sent to enp0s3, you can get the following output:

$ sudo python3 server.py
b"\x08\x00'\xdd\xd7C\x08\x00'\x8euD\x88\xb5Hi"

Send Data Using Raw Sockets

To send data using a socket, use sendall(). The data to be sent must be a byte sequence containing the header and payload of the Ethernet frame.

When sending data “Hi” from enp0s10 (08:00:27:8e:75:44) to enp0s3 (08:00:27:dd:d7:43), the script client.py is as follows:

import socket
ETH_P_ALL = 3
interface = 'enp0s10'
dst = b'\x08\x00\x27\xdd\xd7\x43'  # destination MAC address
src = b'\x08\x00\x27\x8e\x75\x44'  # source MAC address
proto = b'\x88\xb5'                # ethernet frame type
payload = 'Hi'.encode()            # payload
s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
s.bind((interface, 0))
s.sendall(dst + src + proto + payload)
s.close()

Then,

$ sudo python3 client.py
Sent!

Make sure that the server side terminal outputs the data you sent.

See Also