How to Code Raw Sockets in C on Linux

By | July 26, 2020

Raw tcp sockets in C

Raw sockets can be used to construct a packet manually inside an application. In normal sockets when any data is send over the network, the kernel of the operating system adds some headers to it like IP header and TCP header. So an application only needs to take care of what data it is sending and what reply it is expecting.

But there are other cases when an application needs to set its own headers. Raw sockets are used in security related applications like nmap , packets sniffer etc.

In this article we are going to program raw sockets on linux using native sockets.

Windows for example does not support raw socket programming directly. To program raw sockets on windows a packet crafting library like winpcap has to be used.

In this article we are going to do some raw socket programming by constructing a raw TCP packet and sending it over the network. Before programming raw sockets, it is recommended that you learn about the basics of socket programming in c.

Raw TCP packets

A TCP packet is constructed like this

Packet = IP Header + TCP Header + Data

The plus means to attach the binary data side by side. So when making a raw tcp packet we need to know how to construct the headers properly. The structures of all headers are established standards which are described in RFCs.

IP Header Structure

The structure of IP Header as given by 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    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The "Source Address" field stores the ip address of the system sending the packet and the "Destination Address" contains the ip address of the destination system. Ip addresses are stored in long number format. The "Protocol" field stores a number that indicates the protocol, which is TCP in this case.

Structure of TCP header

The structure of a TCP header as given by 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                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

So we need to construct the headers according to the formats specified above.

Raw tcp sockets

Create a raw socket like this

int s = socket (AF_INET, SOCK_RAW, IPPROTO_TCP);

The above function call creates a raw socket of protocol TCP. This means that we have to provide the TCP header along with the data. The kernel or the network stack of Linux shall provide the IP header.

If we want to provide the IP header as well then there are 2 ways of doing this

1. Use protocol IPPROTO_RAW - This will allow to specify the IP header and everything that is contained in the packet.

int s = socket (AF_INET, SOCK_RAW, IPPROTO_RAW);

2. Set the IP_HDRINCL socket option to 1 - This is same as the above. Just another way of doing.

int s = socket (AF_INET, SOCK_RAW, IPPROTO_TCP);

int one = 1;
const int *val = &one;
if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, val, sizeof (one)) < 0)
{
	printf ("Error setting IP_HDRINCL. Error number : %d . Error message : %s \n" , errno , strerror(errno));
	exit(0);
}

When using the IP_HDRINCL the protocol used in the socket function is effectively of no use.

In this example we are creating raw sockets where we specify the Ip header and TCP header. The packet that moves out of the machine actually has 1 more header attached to it called the Ethernet header. So the actual packet structure is somewhat like this.

Packet = Ethernet header + Ip header + TCP header + Data

Take a look at the packets sniffed by wireshark to understand this better. It is important to note here that the Ethernet header is provided by the OS kernel and we do not have to construct it. However it is possible to make such raw packets where we can even specify the ethernet header, but we shall look into those in a separate article.

Below is an example code which constructs a raw TCP packet with some data

Final Code

/*
	Raw TCP packets
*/
#include <stdio.h>	//for printf
#include <string.h> //memset
#include <sys/socket.h>	//for socket ofcourse
#include <stdlib.h> //for exit(0);
#include <errno.h> //For errno - the error number
#include <netinet/tcp.h>	//Provides declarations for tcp header
#include <netinet/ip.h>	//Provides declarations for ip header
#include <arpa/inet.h> // inet_addr
#include <unistd.h> // sleep()

/* 
	96 bit (12 bytes) pseudo header needed for tcp header checksum calculation 
*/
struct pseudo_header
{
	u_int32_t source_address;
	u_int32_t dest_address;
	u_int8_t placeholder;
	u_int8_t protocol;
	u_int16_t tcp_length;
};

/*
	Generic checksum calculation function
*/
unsigned short csum(unsigned short *ptr,int nbytes) 
{
	register long sum;
	unsigned short oddbyte;
	register short answer;

	sum=0;
	while(nbytes>1) {
		sum+=*ptr++;
		nbytes-=2;
	}
	if(nbytes==1) {
		oddbyte=0;
		*((u_char*)&oddbyte)=*(u_char*)ptr;
		sum+=oddbyte;
	}

	sum = (sum>>16)+(sum & 0xffff);
	sum = sum + (sum>>16);
	answer=(short)~sum;
	
	return(answer);
}

int main (void)
{
	//Create a raw socket
	int s = socket (PF_INET, SOCK_RAW, IPPROTO_TCP);
	
	if(s == -1)
	{
		//socket creation failed, may be because of non-root privileges
		perror("Failed to create socket");
		exit(1);
	}
	
	//Datagram to represent the packet
	char datagram[4096] , source_ip[32] , *data , *pseudogram;
	
	//zero out the packet buffer
	memset (datagram, 0, 4096);
	
	//IP header
	struct iphdr *iph = (struct iphdr *) datagram;
	
	//TCP header
	struct tcphdr *tcph = (struct tcphdr *) (datagram + sizeof (struct ip));
	struct sockaddr_in sin;
	struct pseudo_header psh;
	
	//Data part
	data = datagram + sizeof(struct iphdr) + sizeof(struct tcphdr);
	strcpy(data , "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
	
	//some address resolution
	strcpy(source_ip , "192.168.1.2");
	sin.sin_family = AF_INET;
	sin.sin_port = htons(80);
	sin.sin_addr.s_addr = inet_addr ("1.2.3.4");
	
	//Fill in the IP Header
	iph->ihl = 5;
	iph->version = 4;
	iph->tos = 0;
	iph->tot_len = sizeof (struct iphdr) + sizeof (struct tcphdr) + strlen(data);
	iph->id = htonl (54321);	//Id of this packet
	iph->frag_off = 0;
	iph->ttl = 255;
	iph->protocol = IPPROTO_TCP;
	iph->check = 0;		//Set to 0 before calculating checksum
	iph->saddr = inet_addr ( source_ip );	//Spoof the source ip address
	iph->daddr = sin.sin_addr.s_addr;
	
	//Ip checksum
	iph->check = csum ((unsigned short *) datagram, iph->tot_len);
	
	//TCP Header
	tcph->source = htons (1234);
	tcph->dest = htons (80);
	tcph->seq = 0;
	tcph->ack_seq = 0;
	tcph->doff = 5;	//tcp header size
	tcph->fin=0;
	tcph->syn=1;
	tcph->rst=0;
	tcph->psh=0;
	tcph->ack=0;
	tcph->urg=0;
	tcph->window = htons (5840);	/* maximum allowed window size */
	tcph->check = 0;	//leave checksum 0 now, filled later by pseudo header
	tcph->urg_ptr = 0;
	
	//Now the TCP checksum
	psh.source_address = inet_addr( source_ip );
	psh.dest_address = sin.sin_addr.s_addr;
	psh.placeholder = 0;
	psh.protocol = IPPROTO_TCP;
	psh.tcp_length = htons(sizeof(struct tcphdr) + strlen(data) );
	
	int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr) + strlen(data);
	pseudogram = malloc(psize);
	
	memcpy(pseudogram , (char*) &psh , sizeof (struct pseudo_header));
	memcpy(pseudogram + sizeof(struct pseudo_header) , tcph , sizeof(struct tcphdr) + strlen(data));
	
	tcph->check = csum( (unsigned short*) pseudogram , psize);
	
	//IP_HDRINCL to tell the kernel that headers are included in the packet
	int one = 1;
	const int *val = &one;
	
	if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, val, sizeof (one)) < 0)
	{
		perror("Error setting IP_HDRINCL");
		exit(0);
	}
	
	//loop if you want to flood :)
	while (1)
	{
		//Send the packet
		if (sendto (s, datagram, iph->tot_len ,	0, (struct sockaddr *) &sin, sizeof (sin)) < 0)
		{
			perror("sendto failed");
		}
		//Data send successfully
		else
		{
			printf ("Packet Send. Length : %d \n" , iph->tot_len);
		}
        // sleep for 1 seconds
        sleep(1);
	}
	
	return 0;
}

//Complete

Compile and Run

Compile by program by doing a gcc raw_socket.c at the terminal. Remember to run the program with root privileges. Raw sockets require root privileges.

$ gcc raw_socket.c -o raw_socket
$ sudo ./raw_socket

Note the while loop in the above program. It has been put for testing purpose and should be removed if you dont intend to flood the target.

Use a packet sniffer like wireshark to check the output and verify that the packets have actually been generated and send over the network. Also note that if some kind of firewall like firestarter is running then it might block raw packets.

Resources

http://linux.die.net/man/7/raw
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].

39 Comments

How to Code Raw Sockets in C on Linux
  1. Vladyslav

    Hi, thank you for the great example!
    One question. You use direct mapping of the structures into memory. How do you know that there will be no problems with alignment? Did you just check it with “sizeof” beforehand?

  2. Majid

    also you can using below checksum function in your codes instead of this checksum function because this function may not work correctly on all processor architecture.
    https://locklessinc.com/articles/tcp_checksum/

    proposed checksum is second function in this link. you can read descriptions about different functions in above link.

    Thanks.

  3. Majid

    hi dears,
    this code has one problem:
    1. you must send size of IP header for checksum function instead of iph->tot_len because this field is sum of IP and TCP with data part. (IP header size is 20 byte)

    Thanks.

    1. Silver Moon Post author

      if you need to receive the data then it is not recommended to use raw sockets.
      Just use normal tcp or udp sockets to send and receive data.

  4. SAURAV SEN

    sir why datagram is of size 4096…have u taken it randomly or thorugh some calculation??

    struct iphdr *iph = (struct iphdr *) datagram;

    //TCP header
    struct tcphdr *tcph = (struct tcphdr *) (datagram + sizeof (struct ip));

    please explain sir this it will be helpful if u mail me… also

  5. Joseph

    Nice example: I am a little confused about the IPH checksum. you are passing iph->tot_len to csum(), which looks to include the data. I thought the IPH checksum was confined to only the header. Tx.

  6. Alex

    Hello,

    Very good example and explanations. Since I make use of raw sockets to send crafted TCP packets in my program, I was looking for an example on the web for exact variables, structures etc. and after reading many of those, I can tell you sir, Your’s explanation is excellent. Not only you’re mentioning all the correct fields. you’re also demonstrating using it with some excellent example.

    However, since you’re using “malloc” in your example, shouldn’t you “free” it before the program ends?

  7. Nicolai Pascal Großer

    where is the difference between “struct iphdr” and “struct ip”
    Why do you use “struct ip” in line 74 ?

      1. Abhishek Sagar

        I began reading this article, because i wanted to learn how user can define his own L4 layer protocol, but you ended up preparing standard TCP packet. Anyways….
        Suppose if i define some XYZ layer 4 protocol, how the recieving machine would going to unwrap the layer 4 header as it may not aware of this non standard L4 header ??

  8. Vijay Kanta

    Wow, I knew a bit of socket programming with pure C, but this is taking it too deeper. Thanks for letting me know it even existed. :-)

  9. John Lauro

    The code makes some architecture assumptions that will probably work 99% of the time, but isn’t completely portable. For example, the checksum calculation assumes short is 2 bytes, and will likely segfault on anything where it is larger.

  10. patrick h

    Shouldn’t iph->tot_len be hton’d?

    Seems like there are several fields in the headers that need to be hton’d and they dont seem to be present in this example…

    does this example work? or am i missing something?

    1. Silver Moon

      thanks for pointing it out. the total_length value should be passed through htons.
      the code works well because the kernel fills in the correct total length in the ip header.

    1. Silver Moon

      i am not sure about the exact mechanism. the destination address given in sendto is probably used to determine the network route the packet should take. the behaviour could vary across different systems like bsd, linux etc.

    1. Silver Moon

      you have to debug the program and find out the exact point where the segmentation fault is occuring.
      the error might be occuring due to some kind of invalid memory access being done in the code.

  11. Bruce Wayne

    Hey I also want to send some data,say a character string along with the datagram. What changes should I make to the code?

    Thanks. :)

        1. Bruce Wayne

          I run your code and capture the packets using wireshark. But when I try to see the the data using the “follow TCP stream” option, it says “the packets in the capture file for that stream have no data”. I checked the code and everything seems to be fine.
          Is there any hidden reason for this happening?

  12. hazem

    it gives me “[bad hdr length 0 – too short, < 20]" on tcpdump and packets dropped by kernel. what does that mean ?

  13. Raj

    Hey do you need to write an explicit code for receiving these packets on the client side or can wireshark capture them automatically?
    I am sending these packets over wlan0 and am not able to view these packets on wireshark for some reason.
    Could you help me with this?
    I would really appreciate any help you could provide.

    Thanks

    1. Silver Moon Post author

      wireshark will capture the packets. if wireshark is not showing any packets, check if some firewall is blocking
      the packets or not (this happens in most cases).

      1. What up!!!

        Hey it worked. But I am trying to change the TOS values but those changes are not showing up on the packets captured on wireshark. If I change any other fields in the Ip header, it shows that but it is causing some problems with the TOS field.
        I basically want to set the priority of the packets that are going out of the machine. Am I on the right track?
        Please help me with this.

        Thanks.

      2. Sanjeev

        And do you have any idea what changes are to be made in this code to make it work for UDP protocol rather than TCP?

        Thanks

        1. Silver Moon Post author

          change iph->protocol to 17. UDP protocol number is 17
          then replace tcp header with the udp header. rest is the data to be send

Leave a Reply

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