logoCndocs
Models

Raw Sockets

Raw sockets provide direct access to the underlying network protocols, bypassing the normal transport layer (TCP/UDP) processing. They allow applications to construct and process network packets at a lower level, giving developers more control but also requiring more responsibility.

Understanding Raw Sockets

What Are Raw Sockets?

Raw sockets are a special type of socket that allows direct sending and receiving of IP packets without any protocol-specific transport layer formatting. With raw sockets, you can:

  1. Create Custom Protocols: Implement your own transport protocols
  2. Access Protocol Headers: Read and write protocol headers directly
  3. Capture Network Traffic: Sniff packets on the network
  4. Send Custom Packets: Craft packets with specific characteristics
  5. Implement Network Tools: Build tools like ping, traceroute, and network scanners

Privileges Required

Because raw sockets can be used for network attacks and monitoring, they typically require elevated privileges:

  • On Unix-like systems, raw socket creation usually requires root privileges or the CAP_NET_RAW capability
  • On Windows, raw sockets have various restrictions and may require administrator privileges

Socket Types and Protocols

Raw sockets are created using the SOCK_RAW socket type:

#include <sys/socket.h>
 
int sockfd = socket(AF_INET, SOCK_RAW, protocol);

The protocol parameter specifies which protocol's packets you want to work with:

  • IPPROTO_ICMP: ICMP packets (ping)
  • IPPROTO_TCP: TCP packets
  • IPPROTO_UDP: UDP packets
  • IPPROTO_RAW: Raw IP packets (allows sending custom IP packets)

Creating a Raw Socket

Here's how to create a basic raw socket:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
 
int main() {
    int sockfd;
    
    // Create a raw socket for ICMP
    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    
    printf("Raw socket created successfully\n");
    
    // Use the socket...
    
    close(sockfd);
    return 0;
}

To compile and run this program, you'll need root privileges:

gcc -o raw_socket raw_socket.c
sudo ./raw_socket

Packet Headers

When working with raw sockets, you need to understand the structure of network packets. Here are the common header structures:

IP Header

struct iphdr {
    unsigned int ihl:4;        /* Header length */
    unsigned int version:4;    /* Version */
    uint8_t tos;               /* Type of service */
    uint16_t tot_len;          /* Total length */
    uint16_t id;               /* Identification */
    uint16_t frag_off;         /* Fragment offset */
    uint8_t ttl;               /* Time to live */
    uint8_t protocol;          /* Protocol */
    uint16_t check;            /* Checksum */
    uint32_t saddr;            /* Source address */
    uint32_t daddr;            /* Destination address */
    /* Options may follow */
};

ICMP Header

struct icmphdr {
    uint8_t type;              /* Message type */
    uint8_t code;              /* Type sub-code */
    uint16_t checksum;         /* Checksum */
    union {
        struct {
            uint16_t id;       /* Identifier */
            uint16_t sequence; /* Sequence number */
        } echo;
        uint32_t gateway;      /* Gateway address */
        struct {
            uint16_t unused;   /* Unused */
            uint16_t mtu;      /* MTU */
        } frag;
    } un;
};

TCP Header

struct tcphdr {
    uint16_t source;           /* Source port */
    uint16_t dest;             /* Destination port */
    uint32_t seq;              /* Sequence number */
    uint32_t ack_seq;          /* Acknowledgment number */
    uint16_t res1:4;           /* Reserved */
    uint16_t doff:4;           /* Data offset */
    uint16_t fin:1;            /* FIN flag */
    uint16_t syn:1;            /* SYN flag */
    uint16_t rst:1;            /* RST flag */
    uint16_t psh:1;            /* PSH flag */
    uint16_t ack:1;            /* ACK flag */
    uint16_t urg:1;            /* URG flag */
    uint16_t res2:2;           /* Reserved */
    uint16_t window;           /* Window size */
    uint16_t check;            /* Checksum */
    uint16_t urg_ptr;          /* Urgent pointer */
    /* Options may follow */
};

UDP Header

struct udphdr {
    uint16_t source;           /* Source port */
    uint16_t dest;             /* Destination port */
    uint16_t len;              /* UDP length */
    uint16_t check;            /* UDP checksum */
};

Implementing a Simple Ping Tool

Let's implement a simple ping tool using raw sockets to demonstrate how to send and receive ICMP Echo packets:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/time.h>
 
#define PACKET_SIZE 64
#define MAX_WAIT_TIME 5
#define ICMP_ECHO 8
 
// Calculate ICMP checksum
unsigned short calculate_checksum(unsigned short *buf, int len) {
    unsigned long sum = 0;
    
    while (len > 1) {
        sum += *buf++;
        len -= 2;
    }
    
    if (len == 1) {
        sum += *(unsigned char *)buf;
    }
    
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    
    return (unsigned short)(~sum);
}
 
// Send ICMP Echo Request
int send_ping(int sockfd, struct sockaddr_in *dest_addr, int seq_num) {
    char packet[PACKET_SIZE];
    struct icmphdr *icmp = (struct icmphdr *)packet;
    struct timeval *time_val;
    
    // Initialize the packet buffer
    memset(packet, 0, sizeof(packet));
    
    // Fill in ICMP header
    icmp->type = ICMP_ECHO;
    icmp->code = 0;
    icmp->un.echo.id = getpid() & 0xFFFF;
    icmp->un.echo.sequence = seq_num;
    
    // Add timestamp to the packet
    time_val = (struct timeval *)(packet + sizeof(struct icmphdr));
    gettimeofday(time_val, NULL);
    
    // Calculate checksum
    icmp->checksum = 0;
    icmp->checksum = calculate_checksum((unsigned short *)icmp, sizeof(struct icmphdr) + sizeof(struct timeval));
    
    // Send the packet
    if (sendto(sockfd, packet, sizeof(struct icmphdr) + sizeof(struct timeval), 0,
              (struct sockaddr *)dest_addr, sizeof(*dest_addr)) <= 0) {
        perror("sendto failed");
        return -1;
    }
    
    return 0;
}
 
// Receive ICMP Echo Reply
int receive_ping(int sockfd, struct sockaddr_in *dest_addr, int seq_num) {
    char packet[PACKET_SIZE + sizeof(struct iphdr)];
    struct iphdr *ip;
    struct icmphdr *icmp;
    struct timeval *time_sent, time_now, timeout;
    struct sockaddr_in from_addr;
    socklen_t from_len = sizeof(from_addr);
    int bytes_received;
    double elapsed_time;
    
    // Set timeout for receive operation
    timeout.tv_sec = MAX_WAIT_TIME;
    timeout.tv_usec = 0;
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
    
    // Receive packets until we get the right one or timeout
    while (1) {
        bytes_received = recvfrom(sockfd, packet, sizeof(packet), 0,
                                 (struct sockaddr *)&from_addr, &from_len);
        
        if (bytes_received < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                printf("Request timed out\n");
                return -1;
            }
            perror("recvfrom failed");
            return -1;
        }
        
        // Get IP and ICMP headers
        ip = (struct iphdr *)packet;
        icmp = (struct icmphdr *)(packet + (ip->ihl * 4));
        
        // Check if this is an ICMP Echo Reply
        if (icmp->type == ICMP_ECHOREPLY && 
            icmp->un.echo.id == (getpid() & 0xFFFF) &&
            icmp->un.echo.sequence == seq_num) {
            
            // Get the timestamp from the packet
            time_sent = (struct timeval *)(packet + (ip->ihl * 4) + sizeof(struct icmphdr));
            gettimeofday(&time_now, NULL);
            
            // Calculate elapsed time in milliseconds
            elapsed_time = (time_now.tv_sec - time_sent->tv_sec) * 1000.0 +
                          (time_now.tv_usec - time_sent->tv_usec) / 1000.0;
            
            printf("%d bytes from %s: icmp_seq=%d ttl=%d time=%.1f ms\n",
                   bytes_received - (ip->ihl * 4), inet_ntoa(from_addr.sin_addr),
                   icmp->un.echo.sequence, ip->ttl, elapsed_time);
            
            return 0;
        }
    }
    
    return -1;
}
 
int main(int argc, char *argv[]) {
    int sockfd, ret;
    struct sockaddr_in dest_addr;
    
    // Check command line arguments
    if (argc != 2) {
        fprintf(stderr, "Usage: %s <destination_ip>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    // Create raw socket for ICMP
    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    
    // Initialize destination address
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    
    // Convert IP address from string to binary form
    if (inet_pton(AF_INET, argv[1], &dest_addr.sin_addr) <= 0) {
        perror("inet_pton failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    
    printf("PING %s (%s) %d bytes of data.\n", argv[1], argv[1], PACKET_SIZE - sizeof(struct icmphdr));
    
    // Send and receive ping packets
    for (int i = 0; i < 4; i++) {
        ret = send_ping(sockfd, &dest_addr, i);
        if (ret == 0) {
            receive_ping(sockfd, &dest_addr, i);
        }
        sleep(1);
    }
    
    close(sockfd);
    return 0;
}

Packet Sniffing with Raw Sockets

Raw sockets can be used to capture network packets. Here's a simple packet sniffer:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
 
#define BUFFER_SIZE 65536
 
// Function to print packet details
void print_packet(unsigned char *buffer, int size) {
    struct iphdr *ip_header = (struct iphdr *)buffer;
    unsigned short ip_header_len = ip_header->ihl * 4;
    
    // Print IP header information
    printf("\n\n=== IP Header ===\n");
    printf("IP Version: %d\n", ip_header->version);
    printf("IP Header Length: %d bytes\n", ip_header_len);
    printf("Type of Service: %d\n", ip_header->tos);
    printf("Total Length: %d bytes\n", ntohs(ip_header->tot_len));
    printf("Identification: %d\n", ntohs(ip_header->id));
    printf("TTL: %d\n", ip_header->ttl);
    printf("Protocol: %d\n", ip_header->protocol);
    printf("Checksum: 0x%04X\n", ntohs(ip_header->check));
    
    struct in_addr source_addr, dest_addr;
    source_addr.s_addr = ip_header->saddr;
    dest_addr.s_addr = ip_header->daddr;
    
    printf("Source IP: %s\n", inet_ntoa(source_addr));
    printf("Destination IP: %s\n", inet_ntoa(dest_addr));
    
    // Process based on protocol
    switch (ip_header->protocol) {
        case IPPROTO_ICMP:
            printf("Protocol: ICMP\n");
            struct icmphdr *icmp_header = (struct icmphdr *)(buffer + ip_header_len);
            printf("ICMP Type: %d\n", icmp_header->type);
            printf("ICMP Code: %d\n", icmp_header->code);
            break;
            
        case IPPROTO_TCP:
            printf("Protocol: TCP\n");
            struct tcphdr *tcp_header = (struct tcphdr *)(buffer + ip_header_len);
            printf("Source Port: %d\n", ntohs(tcp_header->source));
            printf("Destination Port: %d\n", ntohs(tcp_header->dest));
            printf("Sequence Number: %u\n", ntohl(tcp_header->seq));
            printf("Acknowledgment Number: %u\n", ntohl(tcp_header->ack_seq));
            printf("Header Length: %d bytes\n", tcp_header->doff * 4);
            printf("Flags: ");
            if (tcp_header->fin) printf("FIN ");
            if (tcp_header->syn) printf("SYN ");
            if (tcp_header->rst) printf("RST ");
            if (tcp_header->psh) printf("PSH ");
            if (tcp_header->ack) printf("ACK ");
            if (tcp_header->urg) printf("URG ");
            printf("\n");
            printf("Window Size: %d\n", ntohs(tcp_header->window));
            break;
            
        case IPPROTO_UDP:
            printf("Protocol: UDP\n");
            struct udphdr *udp_header = (struct udphdr *)(buffer + ip_header_len);
            printf("Source Port: %d\n", ntohs(udp_header->source));
            printf("Destination Port: %d\n", ntohs(udp_header->dest));
            printf("Length: %d\n", ntohs(udp_header->len));
            break;
            
        default:
            printf("Protocol: Other (%d)\n", ip_header->protocol);
            break;
    }
    
    printf("=================\n");
}
 
int main() {
    int sockfd;
    unsigned char buffer[BUFFER_SIZE];
    struct sockaddr saddr;
    socklen_t saddr_len = sizeof(saddr);
    
    // Create a raw socket that captures all IP packets
    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    
    // Set the socket option to include the IP header
    int one = 1;
    if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) {
        perror("setsockopt IP_HDRINCL failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    
    // Bind the socket to an interface
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    
    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    
    // Enable promiscuous mode
    struct packet_mreq mr;
    memset(&mr, 0, sizeof(mr));
    mr.mr_type = PACKET_MR_PROMISC;
    if (setsockopt(sockfd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr)) < 0) {
        perror("setsockopt PACKET_MR_PROMISC failed");
        // Continue anyway, as this might not be supported on all systems
    }
    
    printf("Packet sniffer started...\n");
    printf("Press Ctrl+C to stop.\n");
    
    // Capture packets
    while (1) {
        ssize_t data_size = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, &saddr, &saddr_len);
        
        if (data_size < 0) {
            perror("recvfrom failed");
            continue;
        }
        
        print_packet(buffer, data_size);
    }
    
    close(sockfd);
    return 0;
}

Note: This packet sniffer requires root privileges and may not work on all systems due to security restrictions.

Creating Custom IP Packets

To send custom IP packets, you need to construct the entire packet, including the IP header:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
 
// Pseudo header for UDP checksum calculation
struct pseudo_header {
    uint32_t source_address;
    uint32_t dest_address;
    uint8_t placeholder;
    uint8_t protocol;
    uint16_t udp_length;
};
 
// Calculate checksum
unsigned short calculate_checksum(unsigned short *buf, int len) {
    unsigned long sum = 0;
    
    while (len > 1) {
        sum += *buf++;
        len -= 2;
    }
    
    if (len == 1) {
        sum += *(unsigned char *)buf;
    }
    
    sum = (sum >> 16) + (sum & 0xFFFF);
    sum += (sum >> 16);
    
    return (unsigned short)(~sum);
}
 
int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <destination_ip> <destination_port>\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    
    // Create a raw socket
    int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
    if (sockfd < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    
    // Destination address
    struct sockaddr_in dest_addr;
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(atoi(argv[2]));
    
    if (inet_pton(AF_INET, argv[1], &dest_addr.sin_addr) <= 0) {
        perror("inet_pton failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    
    // Packet buffer
    char packet[4096];
    memset(packet, 0, sizeof(packet));
    
    // IP header
    struct iphdr *ip = (struct iphdr *)packet;
    ip->ihl = 5;
    ip->version = 4;
    ip->tos = 0;
    ip->tot_len = sizeof(struct iphdr) + sizeof(struct udphdr) + 10;  // IP + UDP + data
    ip->id = htons(12345);
    ip->frag_off = 0;
    ip->ttl = 64;
    ip->protocol = IPPROTO_UDP;
    ip->check = 0;  // Will be calculated later
    ip->saddr = inet_addr("192.168.1.100");  // Source IP (change to your IP)
    ip->daddr = dest_addr.sin_addr.s_addr;
    
    // Calculate IP header checksum
    ip->check = calculate_checksum((unsigned short *)ip, sizeof(struct iphdr));
    
    // UDP header
    struct udphdr *udp = (struct udphdr *)(packet + sizeof(struct iphdr));
    udp->source = htons(12345);  // Source port
    udp->dest = htons(atoi(argv[2]));  // Destination port
    udp->len = htons(sizeof(struct udphdr) + 10);  // UDP header + data
    udp->check = 0;  // Will be calculated later
    
    // Data
    char *data = packet + sizeof(struct iphdr) + sizeof(struct udphdr);
    strcpy(data, "HelloWorld");
    
    // Calculate UDP checksum
    struct pseudo_header psh;
    psh.source_address = ip->saddr;
    psh.dest_address = ip->daddr;
    psh.placeholder = 0;
    psh.protocol = IPPROTO_UDP;
    psh.udp_length = udp->len;
    
    // Create pseudo packet for checksum calculation
    char *pseudo_packet;
    pseudo_packet = malloc(sizeof(struct pseudo_header) + sizeof(struct udphdr) + 10);
    memcpy(pseudo_packet, &psh, sizeof(struct pseudo_header));
    memcpy(pseudo_packet + sizeof(struct pseudo_header), udp, sizeof(struct udphdr) + 10);
    
    udp->check = calculate_checksum((unsigned short *)pseudo_packet,
                                   sizeof(struct pseudo_header) + sizeof(struct udphdr) + 10);
    
    free(pseudo_packet);
    
    // Send the packet
    if (sendto(sockfd, packet, ip->tot_len, 0,
              (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
        perror("sendto failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }
    
    printf("Packet sent successfully\n");
    
    close(sockfd);
    return 0;
}

Socket Options for Raw Sockets

Several socket options are particularly useful for raw sockets:

IP_HDRINCL

This option tells the kernel that the application will provide the IP header:

int one = 1;
if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) {
    perror("setsockopt IP_HDRINCL failed");
    exit(EXIT_FAILURE);
}

PACKET_MR_PROMISC

This option enables promiscuous mode, allowing the socket to receive all packets on the interface:

struct packet_mreq mr;
memset(&mr, 0, sizeof(mr));
mr.mr_type = PACKET_MR_PROMISC;
if (setsockopt(sockfd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mr, sizeof(mr)) < 0) {
    perror("setsockopt PACKET_MR_PROMISC failed");
    exit(EXIT_FAILURE);
}

IP_RECVTTL

This option allows receiving the TTL value of incoming packets:

int on = 1;
if (setsockopt(sockfd, IPPROTO_IP, IP_RECVTTL, &on, sizeof(on)) < 0) {
    perror("setsockopt IP_RECVTTL failed");
    exit(EXIT_FAILURE);
}

Use Cases for Raw Sockets

  1. Network Monitoring Tools: Packet sniffers, traffic analyzers
  2. Network Diagnostics: Ping, traceroute, MTU discovery
  3. Custom Protocol Implementation: Developing new network protocols
  4. Security Tools: Intrusion detection systems, vulnerability scanners
  5. Network Testing: Stress testing, performance measurement
  6. Educational Purposes: Learning about network protocols
  7. Specialized Applications: VPN implementations, tunneling protocols

Limitations and Considerations

  1. Privileges Required: Raw sockets typically require root/administrator privileges
  2. Platform Differences: Implementation details vary across operating systems
  3. Security Restrictions: Some platforms restrict raw socket functionality
  4. Complexity: Working with raw packets requires detailed protocol knowledge
  5. Responsibility: Applications must handle protocol details correctly
  6. Performance: Processing raw packets can be CPU-intensive
  7. Ethical Considerations: Raw sockets can be used for network attacks

Best Practices

  1. Minimize Privilege: Drop privileges after creating the raw socket
  2. Handle Errors: Implement proper error checking and recovery
  3. Validate Packets: Verify packet integrity before processing
  4. Document Intent: Clearly document why raw sockets are necessary
  5. Consider Alternatives: Use higher-level APIs when possible
  6. Test Thoroughly: Test on different platforms and network configurations
  7. Stay Updated: Keep up with security advisories and platform changes
  8. Respect Privacy: Be mindful of privacy implications when capturing packets
  9. Follow Regulations: Comply with legal requirements for network monitoring
  10. Use Established Libraries: Consider using libpcap or similar libraries

Conclusion

Raw sockets provide powerful capabilities for low-level network programming, allowing developers to work directly with network protocols. While they require more knowledge and responsibility than higher-level sockets, they enable the development of specialized network tools and protocols.

By understanding how to use raw sockets effectively and responsibly, you can leverage their capabilities for legitimate purposes such as network monitoring, diagnostics, and protocol development.

In the next section, we'll explore socket security, including common vulnerabilities and best practices for secure socket programming.

Test Your Knowledge

Take a quiz to reinforce what you've learned

Exam Preparation

Access short and long answer questions for written exams

Share this page