How to Program raw UDP sockets in C on Linux

By | July 30, 2020

Raw UDP sockets

Raw udp sockets are used to constructed udp packets with a application defined custom header. It is useful in security related network applications.

The udp header can be found in RFC 768 and has a very simple structure as shown below.

0      7 8     15 16    23 24    31
+--------+--------+--------+--------+
|     Source      |   Destination   |
|      Port       |      Port       |
+--------+--------+--------+--------+
|                 |                 |
|     Length      |    Checksum     |
+--------+--------+--------+--------+
|
|          data octets ...
+---------------- ...

	User Datagram Header Format

The length is the length of the udp header + data in bytes. So the udp header itself is 8 byte long and data can be upto 65536 byte long. The checksum is calculated the same way as the checksum of a tcp header, using a pseudo header.

The code example shown here is for Linux. Raw socket support is not fully available in the windows socket api (winsock). It is there but with a lot of restrictions.

Examples shown here would construct the IP header along with the udp socket. So its more like a raw IP packet that encapsulates UDP format data inside itself.

Code

A raw UDP socket can be simply created using the socket function

int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

In such a socket, the IP header shall be provided by the kernel. The application has to provide the UDP header + Data.

UDP Header + Data

If the IP_HDRINCL option is set on it, then the application would need to provide the IP header too.

Here is the full program that uses a raw udp socket to send out data.

/*
	Raw UDP sockets
*/
#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/udp.h>	//Provides declarations for udp header
#include<netinet/ip.h>	//Provides declarations for ip header

/* 
	96 bit (12 bytes) pseudo header needed for udp 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 udp_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 of type IPPROTO
	int s = socket (AF_INET, SOCK_RAW, IPPROTO_RAW);
	
	if(s == -1)
	{
		//socket creation failed, may be because of non-root privileges
		perror("Failed to create raw 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;
	
	//UDP header
	struct udphdr *udph = (struct udphdr *) (datagram + sizeof (struct ip));
	
	struct sockaddr_in sin;
	struct pseudo_header psh;
	
	//Data part
	data = datagram + sizeof(struct iphdr) + sizeof(struct udphdr);
	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 ("192.168.1.1");
	
	//Fill in the IP Header
	iph->ihl = 5;
	iph->version = 4;
	iph->tos = 0;
	iph->tot_len = sizeof (struct iphdr) + sizeof (struct udphdr) + strlen(data);
	iph->id = htonl (54321);	//Id of this packet
	iph->frag_off = 0;
	iph->ttl = 255;
	iph->protocol = IPPROTO_UDP;
	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);
	
	//UDP header
	udph->source = htons (6666);
	udph->dest = htons (8622);
	udph->len = htons(8 + strlen(data));	//tcp header size
	udph->check = 0;	//leave checksum 0 now, filled later by pseudo header
	
	//Now the UDP checksum using the pseudo header
	psh.source_address = inet_addr( source_ip );
	psh.dest_address = sin.sin_addr.s_addr;
	psh.placeholder = 0;
	psh.protocol = IPPROTO_UDP;
	psh.udp_length = htons(sizeof(struct udphdr) + strlen(data) );
	
	int psize = sizeof(struct pseudo_header) + sizeof(struct udphdr) + strlen(data);
	pseudogram = malloc(psize);
	
	memcpy(pseudogram , (char*) &psh , sizeof (struct pseudo_header));
	memcpy(pseudogram + sizeof(struct pseudo_header) , udph , sizeof(struct udphdr) + strlen(data));
	
	udph->check = csum( (unsigned short*) pseudogram , psize);
	
	//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);
		}
	}
	
	return 0;
}

//Complete

Run the program with root user/privileges.
On Ubuntu use sudo.

$ gcc raw_udp.c
$ sudo ./a.out

Notes

To check the raw udp packets, use a packets sniffer like Wireshark. It should show all the packets generated by the above program.

If you only need to construct the UDP header then use IPPROTO_UDP as the protocol. Firewalls like firestarter can block such raw packets from transmitting, so disable them before testing the program.

A common application of raw udp sockets is udp based tracerouting where an application sends out udp packets with increasing ttl values in Ip header. We shall see an example of such a program in another article.

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].

9 Comments

How to Program raw UDP sockets in C on Linux
  1. Stefan

    Hi madmat,

    Sorry, I think you’re wrong:
    You oversee, that a value >0xFFFF can be appear even after the first sum. So the addition of the bits >0xFFFF have to be done twice.
    As an example, think about checksum of 0x3FFFF after addition of all bytes to be checksummed.
    The first wrap gives you 3 + 0xFFFF = 0x10002, the second wrap gives you 1 + 2 = 0x0003 as final checksum.
    This rule is valid for both, IP and UDP.

    regards,
    Stefan

  2. madmat

    in csum() I found a small error here:

    sum = (sum>>16)+(sum & 0xffff);
    sum = sum + (sum>>16); // this line is useless
    I rebuild csum() a little:

    uint16_t csum(void *buf, int cb)
    {
    uint16_t *ptr = (uint16_t *)buf;
    //initialize the sum with the last byte in case of odd size, otherwise to zero
    int32_t sum = (cb&1)?((uint8_t)buf)[cb-1]:0;
    cb/=2;
    while(cb–) sum+=*ptr++;
    return (uint16_t)~((sum>>16)+(sum & 0xffff));
    }
    sould work the same way

  3. mljli

    For anyone got UDP checksum error, just swap the order of placeholder and protocol in the pseudo_header struct to match the endianness of your OS.

  4. Fatemah Alharbi

    Thanks for the code but I keep getting error on my UDP checksum calculation. How can I fix that? The IP checksum is correct though!

  5. Viktor Przebinda

    Also agree with Daniel, tcpdump was giving checksum errors until I changed csum to use sizeof (struct iphdr).

  6. James

    I agree with Daniel, if the data is different as I needed it to be, you must change the code to use this for IP header checksum: iph->check = csum ((unsigned short *) datagram, sizeof (struct iphdr));

  7. Daniel

    First of all, you should really change all the comments that say “TCP” to “UDP”. It looks like you’ve just copied some TCP code. Secondly, the IP header checksum is supposed to only sum the IP header itself, whereas in your code it sums the entire datagram. This may still work, but only because the rest of the datagram is set to 0. To do it correctly, you should only sum the length of the IP header.

    1. Silver Moon Post author

      the ip checksum is being calculated like this
      iph->check = csum ((unsigned short *) datagram, iph->tot_len);

      the second parameter is the ip header length which specifies how much of the whole datagram should be used to calculate the checksum, which is only the ip header in this case.

Leave a Reply

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