Raw Socket Programming in Python on Linux – Code Examples

By | January 13, 2023

Raw sockets

Raw sockets allow a program or application to provide custom headers for the specific protocol(tcp ip) which are otherwise provided by the kernel/os network stack.

In more simple terms its for adding custom headers instead of headers provided by the underlying operating system.

Raw socket support is available natively in the socket api in linux. This is different from windows where it is absent (it became available in windows 2000/xp/xp sp1 but was removed later).

Although raw sockets don't find much use in common networking applications, they are used widely in applications related to network security.

TCP IP Headers

In this article we are going to create raw tcp/ip packets. For this we need to know how to make proper ip header and tcp headers. A packet = Ip header + Tcp header + data.

So lets have a look at the structures.

Ip header

According to RFC 791

0                   1                   2                   3   
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version|  IHL  |Type of Service|          Total Length         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Identification        |Flags|      Fragment Offset    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Time to Live |    Protocol   |         Header Checksum       |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                       Source Address                          |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Destination Address                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Every single number is 1 bit. So for example the Version field is 4 bit. The header must be constructed exactly like shown.

TCP header

Next comes the TCP header. According to RFC 793

0                   1                   2                   3   
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          Source Port          |       Destination Port        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        Sequence Number                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Acknowledgment Number                      |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Data |           |U|A|P|R|S|F|                               |
   | Offset| Reserved  |R|C|S|S|Y|I|            Window             |
   |       |           |G|K|H|T|N|N|                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           Checksum            |         Urgent Pointer        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                             data                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Create a raw socket

Raw socket can be created in python like this

#create a raw socket
try:
	s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
except socket.error , msg:
	print 'Socket could not be created. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
	sys.exit()

To create raw socket, the program must have root privileges on the system. For example on ubuntu run the program with sudo.

The above example creates a raw socket of type IPPROTO_RAW which is a raw IP packet. Means that we provide everything including the ip header.

Once the socket is created, next thing is to create and construct the packet that is to be send out. C like structures are not available in python, therefore the functions called pack and unpack have to be used to create the packet in the structure specified above.

IP Header

So first, lets make the IP Header:

source_ip = '192.168.1.101'
dest_ip = '192.168.1.1'	# or socket.gethostbyname('www.google.com')

# ip header fields
ip_ihl = 5
ip_ver = 4
ip_tos = 0
ip_tot_len = 0	# kernel will fill the correct total length
ip_id = 54321	#Id of this packet
ip_frag_off = 0
ip_ttl = 255
ip_proto = socket.IPPROTO_TCP
ip_check = 0	# kernel will fill the correct checksum
ip_saddr = socket.inet_aton ( source_ip )	#Spoof the source ip address if you want to
ip_daddr = socket.inet_aton ( dest_ip )

ip_ihl_ver = (version << 4) + ihl

# the ! in the pack format string means network order
ip_header = pack('!BBHHHBBH4s4s' , ip_ihl_ver, ip_tos, ip_tot_len, ip_id, ip_frag_off, ip_ttl, ip_proto, ip_check, ip_saddr, ip_daddr)

Now ip_header has the data for the ip header. Now the usage of pack function, it packs some values has bytes, some as 16bit fields and some as 32 bit fields.

Tcp Header

Next comes the tcp header:

# tcp header fields
tcp_source = 1234	# source port
tcp_dest = 80	# destination port
tcp_seq = 454
tcp_ack_seq = 0
tcp_doff = 5	#4 bit field, size of tcp header, 5 * 4 = 20 bytes
#tcp flags
tcp_fin = 0
tcp_syn = 1
tcp_rst = 0
tcp_psh = 0
tcp_ack = 0
tcp_urg = 0
tcp_window = socket.htons (5840)	#	maximum allowed window size
tcp_check = 0
tcp_urg_ptr = 0

tcp_offset_res = (tcp_doff << 4) + 0
tcp_flags = tcp_fin + (tcp_syn << 1) + (tcp_rst << 2) + (tcp_psh <<3) + (tcp_ack << 4) + (tcp_urg << 5)

# the ! in the pack format string means network order
tcp_header = pack('!HHLLBBHHH' , tcp_source, tcp_dest, tcp_seq, tcp_ack_seq, tcp_offset_res, tcp_flags,  tcp_window, tcp_check, tcp_urg_ptr)

The construction of the tcp header is similar to the ip header. The tcp header has a field called checksum which needs to be filled in correctly. A pseudo header is constructed to compute the checksum.

The checksum is calculated over the tcp header along with the data. Checksum is necessary to detect errors in the transmission on the receiver side.

Code

Here is the full code to send a raw packet

'''
	Raw sockets on Linux
'''

# some imports
import socket, sys, time
from struct import *

# checksum functions needed for calculation checksum
def checksum(msg):
	s = 0
	
	# loop taking 2 characters at a time
	for i in range(0, len(msg), 2):
		w = ord(msg[i]) + (ord(msg[i+1]) << 8 )
		s = s + w
	
	s = (s>>16) + (s & 0xffff);
	s = s + (s >> 16);
	
	#complement and mask to 4 byte short
	s = ~s & 0xffff
	
	return s

# the main function
def main():

    #create a raw socket
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
    except socket.error , msg:
        print 'Socket could not be created. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
        sys.exit()

    # tell kernel not to put in headers, since we are providing it, when using IPPROTO_RAW this is not necessary
    # s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
        
    # now start constructing the packet
    packet = '';

    source_ip = '1.2.3.4'
    dest_ip = '192.168.1.1'	# or socket.gethostbyname('www.google.com')

    # ip header fields
    ip_ihl = 5
    ip_ver = 4
    ip_tos = 0
    ip_tot_len = 0	# kernel will fill the correct total length
    ip_id = 54321	#Id of this packet
    ip_frag_off = 0
    ip_ttl = 255
    ip_proto = socket.IPPROTO_TCP
    ip_check = 0	# kernel will fill the correct checksum
    ip_saddr = socket.inet_aton ( source_ip )	#Spoof the source ip address if you want to
    ip_daddr = socket.inet_aton ( dest_ip )

    ip_ihl_ver = (ip_ver << 4) + ip_ihl

    # the ! in the pack format string means network order
    ip_header = pack('!BBHHHBBH4s4s' , ip_ihl_ver, ip_tos, ip_tot_len, ip_id, ip_frag_off, ip_ttl, ip_proto, ip_check, ip_saddr, ip_daddr)

    # tcp header fields
    tcp_source = 1234	# source port
    tcp_dest = 80	# destination port
    tcp_seq = 454
    tcp_ack_seq = 0
    tcp_doff = 5	#4 bit field, size of tcp header, 5 * 4 = 20 bytes
    #tcp flags
    tcp_fin = 0
    tcp_syn = 1
    tcp_rst = 0
    tcp_psh = 0
    tcp_ack = 0
    tcp_urg = 0
    tcp_window = socket.htons (5840)	#	maximum allowed window size
    tcp_check = 0
    tcp_urg_ptr = 0

    tcp_offset_res = (tcp_doff << 4) + 0
    tcp_flags = tcp_fin + (tcp_syn << 1) + (tcp_rst << 2) + (tcp_psh <<3) + (tcp_ack << 4) + (tcp_urg << 5)

    # the ! in the pack format string means network order
    tcp_header = pack('!HHLLBBHHH' , tcp_source, tcp_dest, tcp_seq, tcp_ack_seq, tcp_offset_res, tcp_flags,  tcp_window, tcp_check, tcp_urg_ptr)

    user_data = 'Hello, how are you'

    # pseudo header fields
    source_address = socket.inet_aton( source_ip )
    dest_address = socket.inet_aton(dest_ip)
    placeholder = 0
    protocol = socket.IPPROTO_TCP
    tcp_length = len(tcp_header) + len(user_data)

    psh = pack('!4s4sBBH' , source_address , dest_address , placeholder , protocol , tcp_length);
    psh = psh + tcp_header + user_data;

    tcp_check = checksum(psh)
    #print tcp_checksum

    # make the tcp header again and fill the correct checksum - remember checksum is NOT in network byte order
    tcp_header = pack('!HHLLBBH' , tcp_source, tcp_dest, tcp_seq, tcp_ack_seq, tcp_offset_res, tcp_flags,  tcp_window) + pack('H' , tcp_check) + pack('!H' , tcp_urg_ptr)

    # final full packet - syn packets dont have any data
    packet = ip_header + tcp_header + user_data
    
    # increase count to send more packets
    count = 3
    
    for i in range(count):
        print 'sending packet...'
        # Send the packet finally - the port specified has no effect
        s.sendto(packet, (dest_ip , 0 ))	# put this in a loop if you want to flood the target 
        print 'send'
        time.sleep(1)
        
    print 'all packets send';

main()

Output

Run the above program from the command line using python. Make sure to run with root privileges using sudo, since raw sockets need root privileges.

$ sudo python raw_socket.py 
sending packet...
send
sending packet...
send
sending packet...
send
all packets send
$

Run the above program from the terminal and check the network traffic using a packet sniffer like wireshark. It should show the packet.

You could also use tcpdump in a separate terminal to check that the packets were indeed sent. We need to provide the filter expression to tcpdump to check for the exact packet that we generated from our python program.

Here is a quick example of how it would look:

$ sudo tcpdump -n -i enp1s0 'tcp and src 1.2.3.4'
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp1s0, link-type EN10MB (Ethernet), capture size 262144 bytes
16:50:10.228944 IP 1.2.3.4.1234 > 192.168.1.1.80: Flags [S], seq 454:472, win 53270, length 18: HTTP
16:50:11.230101 IP 1.2.3.4.1234 > 192.168.1.1.80: Flags [S], seq 454:472, win 53270, length 18: HTTP
16:50:12.231100 IP 1.2.3.4.1234 > 192.168.1.1.80: Flags [S], seq 454:472, win 53270, length 18: HTTP

In the above tcpdump output note that the source ip is 1.2.3.4 which we used in our program. To learn more about tcpdump check this post:
Tcpdump Tutorial - How to Sniff and Analyse Packets from Commandline

Conclusion

Raw sockets find application in the field of network security. The above example can be used to code a tcp syn flood program.

Syn flood programs are used in Dos attacks. Raw sockets are also used to code packet sniffers, port scanners etc.

About Silver Moon

A Tech Enthusiast, Blogger, Linux Fan and a Software Developer. Writes about Computer hardware, Linux and Open Source software and coding in Python, Php and Javascript. He can be reached at [email protected].

27 Comments

Raw Socket Programming in Python on Linux – Code Examples
  1. me

    On reading the comments, it is clear that you don’t understand your code. You copied bits from different parts. And the result is incorrect code that confuses everybody.

    1. Visitor

      The reason for everyone’s difficulty is due to the fact that the socket being used is operating at the wrong TCP/IP layer.

      TCP/IP.

      Application (Web/HTTP, FTP, SSH, Multiplayer games, etc…)
      v
      Transport (TCP, UDP, …)
      v
      Network (IP, IPX, …)
      v
      Link (Ethernet, 802.11 partially)

      The hundreds of network protocols which make up our interconnected world can to all some degree be placed with in this stack, some of the most important are shown above. It is called a stack because each layer provides services to the one above and uses services provided by those below it.

      The code on this page attempts to send data from network layer up (hence the need for creating an IP packet). However, the socket is configured as AF_INET which already provides the necessary functions for creating and sending IP packets. The result is that the code puts an IP packet with in another IP packet. Wireshark will not recognize the inner IP packet. The solution here is to either just send the TCP data or to change the socket’s config.

      On Linux, you should use…

      s = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW)

      Apart from this, the code is very helpful, thanks!!

      1. me

        Thank you very much for your knowledgeable input.
        What we want is to spoof the IP, so, we do need to craft the IP packet. Therefore, I think the only option left is, as you say, to use AF_PACKET instead of INET

        A thing that I would have liked to see in action is useful DATA sent instead of writing “Hello how are you” to a web page. I suppose http get code would have been much more interesting. Anyone can do that and see what reply we get?

  2. Cosimo

    Hi, thanks for the article!

    I was just wondering is you can suggest or point me to an example which uses python classes to achieve the same objective.
    I’ve seen a lot of sniffers take advantage of classes, but no example of sending packets.
    Thank you!
    Cosimo

  3. visitor

    You really need a better comment posting mechanism
    that allows your visitors to easily post python code
    without destroying the indentation.

    I’m really really disgusted, and i would not trust your
    code with a monkey I did not like.

  4. visitor

    Last try: code tag and a line start marker to try to preserve spaces:


    ! def checksum(msg):
    ! vals=map(ord,msg)
    ! if ((len(vals)>>1)<<1) != len(vals): vals.append(0) # odd length
    ! s=sum([vals[i]+(vals[i+1]< 0xffff: s=(s>>16)+(s & 0xffff)
    ! return s ^ 0xffff # ones complement

  5. visitor

    OK, let’s try the code tag.


    def checksum(msg):
    vals=map(ord,msg)
    if ((len(vals)>>1)<<1) != len(vals): vals.append(0) # odd length
    s=sum([vals[i]+(vals[i+1]< 0xffff: s=(s>>16)+(s & 0xffff)
    return s ^ 0xffff # ones complement

  6. visitor

    Fix indentation and code mangling by your crappy posting system:

    > def checksum(msg):
    > vals=map(ord,msg)
    > if ((len(vals)>>1)< s=sum([vals[i]+(vals[i+1]< while s > 0xffff: s=(s>>16)+(s & 0xffff)
    > return s ^ 0xffff # ones complement

  7. visitor

    Simplified, clarified, hardened, and corrected(?) checksum function:

    def checksum(msg):
    vals=map(ord,msg)
    if ((len(vals)>>1)<<1) != len(vals): vals.append(0) # odd length
    s=sum([vals[i]+(vals[i+1]< 0xffff: s=(s>>16)+(s & 0xffff)
    return s ^ 0xffff # unsigned ones complement

    This assumes the proper checksum is defined as a 16 bit sum reduction
    followed by a bitwise complement. The original code is buggy.

    1) It fails to prevent an indexing error for odd length messages.
    2) It fails to consistently compute the checksum for very long messages.
    3) Its ‘ones complement’ is an integer signed ones complement, not an unsigned binary complement.

    If the rest of the code is as bad, user beware. One useful thing I did learn
    was the necessity of administrator privilege for creating this socket.

  8. jeff

    I don’t understand your TCP checksum function, at the end, you can simplify it with only ;

    s = s + (s >> 16) # add the carry to the result
    s = ~s & 0xffff # one complement and mask to 4 byte short

    This line isn’t useful : s = (s>>16) + (s & 0xffff)

  9. aaron

    I am trying to captures these packets in wireshark after running the above code by putting the filet ip.src == 192.168.1.101. But I don’t see any packets. I changed the destination address in the above code to my PC IP address.

  10. Aaron

    I executed this code. I changed the destination IP to my system IP. I’m trying to capture packets in wireshark by filtering ip.src ==192.168.1.101 but I’m not receiving any packets.

  11. aaron

    Is it possible to send ICMP using the same socket? OR do we need to replace IPPROTO_RAW with IPPROTO_ICMP_TCP? Also is it the same way we receive the raw socket?

  12. Patrick Leedom

    Why does the Ethernet frame not have any MAC addresses? I thought the kernel handled all of the ethernet headers.

      1. Patrick Leedom

        I was sending it to loopback and the kernel doesnt add a mac since it doesnt go through a nic. My mistake.

Leave a Reply

Your email address will not be published. Required fields are marked *