The Network Layer is responsible for getting your data from one machine to another โ even across thousands of routers spread across the globe. The protocol that does this job in the TCP/IP suite is IP (Internet Protocol). This page explains what IP does, what it does NOT do, and the difference between IPv4 and IPv6.
The network layer sits between the Transport layer (TCP/UDP) and the Data Link layer. Its three core responsibilities are:
The main protocol at this layer in the TCP/IP suite is IP โ Internet Protocol. Every device connected to the internet runs IP.
The primary reason IPv6 was created was the exhaustion of IPv4 addresses. Because of the way addresses were structured and allocated historically, the ~4.3 billion address space ran out far sooner than expected.
The IP header has a 4-bit version field. Version 4 = IPv4. Version number 5 was already assigned to an experimental protocol called Internet Stream Protocol (ST-II), designed for voice/video transmission in the 1970s. Since 5 was taken, the next version of IP was numbered 6.
IP does not send a continuous stream of bytes. It sends individual, independent units called datagrams (also called packets). Each datagram is self-contained: it carries its own source and destination addresses in the header.
Important: each datagram traveling from source to destination may take a completely different route through the internet. There is no guarantee they arrive in order. This is by design โ it makes the network resilient.
These two properties are the most important things to understand about IP. They explain why TCP exists.
IP does NOT establish a virtual circuit between sender and receiver before sending data. There is no “setup” phase. Each datagram is independent โ it is sent into the network and IP just tries its best to deliver it.
Compare to a phone call (connection-oriented): you must dial and connect before talking. IP is more like dropping letters in a mailbox โ each letter is independent.
IP makes a “best effort” but gives no guarantees:
- Packets may arrive out of order
- Packets may be duplicated
- Packets may be lost entirely
- Packets with header errors are silently discarded
- No error recovery is performed by IP
This is not a flaw โ it’s a deliberate design choice. Keeping IP simple and fast allows it to handle enormous amounts of traffic. Reliability is the job of higher layers:
- TCP adds reliability on top of IP (sequencing, retransmission, acknowledgements)
- UDP applications can implement their own reliability if needed
Normally, your application uses TCP (SOCK_STREAM) or UDP (SOCK_DGRAM), and the kernel handles the IP layer for you. But Linux also supports raw sockets (SOCK_RAW), which let your application communicate directly with the IP layer โ bypassing TCP and UDP entirely.
Raw sockets are used for tools like ping (uses ICMP via raw socket), traceroute, network scanners, and packet crafting tools. Creating raw sockets requires root privileges (CAP_NET_RAW capability).
/* raw_socket_example.c
* Creates a raw socket at the IP level (requires root / CAP_NET_RAW)
* This lets you send/receive IP datagrams directly,
* bypassing TCP and UDP.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* struct iphdr โ IP header */
int main(void)
{
int raw_fd;
/*
* AF_INET = IPv4
* SOCK_RAW = bypass TCP/UDP; work directly with IP
* IPPROTO_TCP = we want to see TCP packets
* (use IPPROTO_UDP for UDP, IPPROTO_ICMP for ICMP)
*/
raw_fd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (raw_fd == -1) {
perror("socket(SOCK_RAW) โ are you root?");
exit(EXIT_FAILURE);
}
printf("Raw socket created: fd=%d\n", raw_fd);
/* With IP_HDRINCL option, we can build our own IP header */
int opt = 1;
if (setsockopt(raw_fd, IPPROTO_IP, IP_HDRINCL, &opt, sizeof(opt))) {
perror("setsockopt IP_HDRINCL");
close(raw_fd);
exit(EXIT_FAILURE);
}
/*
* Now you can recv() raw IP datagrams including the IP header.
* struct iphdr gives you direct access to version, ttl, protocol,
* src and dst addresses, etc.
*
* Note: Most application code uses SOCK_STREAM (TCP) or
* SOCK_DGRAM (UDP) instead of SOCK_RAW.
* Raw sockets are for specialized tools: ping, traceroute,
* network scanners, custom protocol implementations.
*/
char buf[65536];
struct sockaddr_in src_addr;
socklen_t addr_len = sizeof(src_addr);
printf("Listening for raw IP packets (Ctrl+C to stop)...\n");
while (1) {
ssize_t n = recvfrom(raw_fd, buf, sizeof(buf), 0,
(struct sockaddr *)&src_addr, &addr_len);
if (n < 0) { perror("recvfrom"); break; }
struct iphdr *iph = (struct iphdr *)buf;
printf("Got IP datagram: protocol=%d, total_len=%d, ttl=%d\n",
iph->protocol, ntohs(iph->tot_len), iph->ttl);
}
close(raw_fd);
return 0;
}
/* Compile and run as root:
gcc -o raw_socket raw_socket_example.c
sudo ./raw_socket
*/
SOCK_RAW is a socket type that lets an application send and receive data directly at the IP layer, bypassing TCP and UDP. It requires root privileges. Use cases include: implementing ping (uses ICMP), implementing traceroute, network scanners (e.g., nmap), and custom protocol implementations.