RAW Sockets in BSD and Linux

Published 09-25-2019 08:00:00

In this series of blog posts, I’d like to talk a little bit about socket programming in python, from the point of view of a network engineer.

In this post, I’ll talk about the IP Header “Total Length” in context of raw sockets.

Introduction

Python provides a thin wrapper around the system calls to the socket API. You can find the reference documentation here

Setting up a RAW socket for sending

There is a protocol we can used called socket.SOCK_RAW. When we create a new socket with socket.SOCK_RAW, we are telling the Kernel that we will pass along the correctly constructed packet.

Example:

raw_sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)

socket.AF_INET is telling the Kernel that you want to send an *A*ddress *F*amily of INET.

However socket.SOCK_RAW is now telling the Kernel that you want to construct the IP header yourself (A note on this in a moment).

Lastly socket.IPPROTO_RAW is now telling the Kernel that you want to construct your own transport protocol. The value put in here will be inserted into the IP Packet as the Protocol, unless you set socket.IPPROTO_RAW, in which case you will set the Protocol number yourself.

A note on socket.SOCK_RAW

On Linux, when you set socket.SOCK_RAW, this automatically sets a varliable in the Kernel called socket.IP_HDRINCL to 1.

IP_HDRINCL is a variable that controls if the Kernel is expecting to get a full and complete IP Packet from you. 1 means “yes”, 0 means you wont construct the IP Packet yourself.

However on BSD, IP_HDRINCL is ALWAYS 0 unless you explicitly set this.

So if you are sending a full IP Packet and Protocol Frame, and IP_HDRINCL is 0, the Kernel will pad your IP Packet and Protocol Frame inside another IP Packet. This is obviously not good.

This means you should always, for the sake of clarity, set IP_HDRINCL:

raw_sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

Total Length

In the IP Header there are two length fields - IP Header Length (ip_ihl) and Total Length (ip_tot_len).

ip_ihl controls the length of the header, or another way of putting it as per RFC:


  IHL:  4 bits

    Internet Header Length is the length of the internet header in 32
    bit words, and thus points to the beginning of the data.  Note that
    the minimum value for a correct header is 5.

ip_tot_len, however, covers the entire packet length:

  Total Length:  16 bits

    Total Length is the length of the datagram, measured in octets,
    including internet header and data.  

In Linux, if you set ip_tot_len to 0, the Kernel will figure out the length for you automatically.

BSD, on the other hand, will never calculate this value for you. You MUST calculate this yourself, after constructing your Protocol packet, and whatever payload it has.

If you do not do this, you’ll see a very unhelpful message from the Kernel:

OSError: [Errno 22] Invalid argument

tl;dr

If you get OSError: [Errno 22] Invalid argument on BSD, and you’re using RAW Sockets, make sure you’re setting ip_tot_len correctly