Every device on a TCP/IP network must have a unique IP address. But an IP address is not just a flat number โ it is split into two parts: a network ID (which network is the device on?) and a host ID (which device on that network?). Understanding this split is the foundation of subnetting, routing, and socket programming with bind() and connect().
An IPv4 address is 32 bits (4 bytes) long. It is made up of two logical parts:
- Network ID โ identifies which network the host is on (like a street name)
- Host ID โ identifies the specific host on that network (like a house number)
In human-readable form, IPv4 addresses are written in dotted-decimal notation: each of the 4 bytes is written as a decimal number (0โ255), separated by dots. Example: 204.152.189.116.
When an organization gets assigned a block of IPv4 addresses, it receives two things:
- A network address (e.g.,
204.152.189.0) - A network mask (e.g.,
255.255.255.0)
The network mask in binary has a sequence of 1s on the left (indicating the network ID portion) followed by a sequence of 0s on the right (indicating the host ID portion available for the organization to assign).
To find the Network ID from an IP address, you AND the address with the mask:
/* Computing network address using bitwise AND */
uint32_t ip = 0xCC9CBD74; /* 204.152.189.116 */
uint32_t mask = 0xFFFFFF00; /* 255.255.255.0 */
uint32_t network = ip & mask;
/* Result: 0xCC9CBD00 = 204.152.189.0 โ the network address */
/* The host part: */
uint32_t host = ip & (~mask);
/* Result: 0x00000074 = 116 โ the host number */
Writing out a full mask like 255.255.255.0 every time is verbose. The CIDR (Classless Inter-Domain Routing) notation solves this by appending a slash and the number of 1 bits in the mask:
The /24 tells you that 24 of the 32 bits are the network ID, leaving 8 bits for host IDs.
bind(), means “listen on all interfaces.” When used as a source address, means “any local address.”10.x.x.x
172.16-31.x.x
When you write socket code, you constantly convert between human-readable IP strings and binary network-order integers. The key functions are inet_pton() (text to binary) and inet_ntop() (binary to text). You also need byte-order conversion.
/* ip_address_demo.c
* Demonstrates IP address conversion and subnet calculations
* in C socket programming.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h> /* inet_pton, inet_ntop, htonl, ntohl */
#include <sys/socket.h>
#include <netinet/in.h>
/* ---- Helper: print an IPv4 address in dotted decimal ---- */
void print_ip(const char *label, uint32_t addr_host_order)
{
/* inet_ntop works with network byte order, so we convert */
uint32_t net = htonl(addr_host_order);
char str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &net, str, sizeof(str));
printf("%-20s: %s (0x%08X)\n", label, str, addr_host_order);
}
int main(void)
{
/* ===== 1. Convert human-readable string to binary ===== */
const char *ip_str = "204.152.189.116";
struct in_addr bin;
/*
* inet_pton: presentation (text) to network (binary)
* Stores result in NETWORK byte order (big-endian)
* Returns 1 on success, 0 if invalid format, -1 on error
*/
if (inet_pton(AF_INET, ip_str, &bin) != 1) {
fprintf(stderr, "Invalid IPv4 address: %s\n", ip_str);
exit(EXIT_FAILURE);
}
printf("String: %s\n", ip_str);
printf("Binary (network order, hex): 0x%08X\n\n", ntohl(bin.s_addr));
/* ===== 2. Convert binary back to string ===== */
char out_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &bin, out_str, sizeof(out_str));
printf("Back to string: %s\n\n", out_str);
/* ===== 3. Byte order: network (big-endian) vs host order ===== */
/*
* Network byte order is always big-endian.
* x86/ARM Linux is little-endian (host order).
* Always convert when filling sockaddr structures:
* htons() = host to network short (16-bit, for port numbers)
* htonl() = host to network long (32-bit, for IP addresses)
* ntohs() = network to host short
* ntohl() = network to host long
*/
uint16_t port = 8080;
printf("Port in host order: %u (0x%04X)\n", port, port);
printf("Port in network order:%u (0x%04X)\n\n",
htons(port), htons(port));
/* ===== 4. Subnet calculation using network mask ===== */
uint32_t ip_addr = ntohl(bin.s_addr); /* convert to host order for arithmetic */
uint8_t cidr = 24; /* /24 means 24 bits of network ID */
uint32_t mask = (cidr == 0) ? 0 : (~0u << (32 - cidr));
uint32_t network = ip_addr & mask; /* AND gives network address */
uint32_t broadcast = network | (~mask); /* OR inverted mask = broadcast */
uint32_t first_host= network + 1;
uint32_t last_host = broadcast - 1;
uint32_t num_hosts = (~mask) - 1; /* 2^host_bits - 2 */
printf("=== Subnet Calculation for %s/%u ===\n", ip_str, cidr);
print_ip("Network address", network);
print_ip("Subnet mask", mask);
print_ip("Broadcast address", broadcast);
print_ip("First host", first_host);
print_ip("Last host", last_host);
printf("%-20s: %u\n", "Usable hosts", num_hosts);
/* ===== 5. Check if an IP is in the same subnet ===== */
const char *test_ip_str = "204.152.189.200";
struct in_addr test_bin;
inet_pton(AF_INET, test_ip_str, &test_bin);
uint32_t test_addr = ntohl(test_bin.s_addr);
printf("\nIs %s in the same /24 subnet? %s\n",
test_ip_str,
(test_addr & mask) == network ? "YES" : "NO");
return 0;
}
/* Compile and run:
gcc -o ip_demo ip_address_demo.c
./ip_demo
Expected output:
String: 204.152.189.116
Binary (network order, hex): 0xCC9CBD74
Back to string: 204.152.189.116
Port in host order: 8080 (0x1F90)
Port in network order:36895 (0x901F) โ bytes are swapped!
=== Subnet Calculation for 204.152.189.116/24 ===
Network address : 204.152.189.0 (0xCC9CBD00)
Subnet mask : 255.255.255.0 (0xFFFFFF00)
Broadcast address : 204.152.189.255 (0xCC9CBDFF)
First host : 204.152.189.1 (0xCC9CBD01)
Last host : 204.152.189.254 (0xCC9CBDFE)
Usable hosts : 254
Is 204.152.189.200 in the same /24 subnet? YES
*/
In practice, IP addresses appear in socket programming whenever you call bind() (server) or connect() (client). Here is the pattern you will use constantly:
/* socket_bind_connect.c
* Shows how IP addresses are used in bind() and connect()
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
void server_example(void)
{
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(8080); /* always use htons for port */
/*
* Three choices for sin_addr:
*
* 1. INADDR_ANY (0.0.0.0) โ bind to ALL network interfaces
* This is the most common choice for a server.
*/
addr.sin_addr.s_addr = htonl(INADDR_ANY);
/*
* 2. Specific IP โ bind only to one interface
* inet_pton(AF_INET, "192.168.1.10", &addr.sin_addr);
*
* 3. Loopback โ only accept local connections
* addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 127.0.0.1
*/
if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
perror("bind");
} else {
printf("Server bound to 0.0.0.0:8080 (all interfaces)\n");
}
close(server_fd);
}
void client_example(const char *server_ip, uint16_t port)
{
int client_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
/* inet_pton converts "192.168.1.10" string to 32-bit binary in network order */
if (inet_pton(AF_INET, server_ip, &server_addr.sin_addr) != 1) {
fprintf(stderr, "Invalid server IP: %s\n", server_ip);
close(client_fd);
return;
}
printf("Connecting to %s:%u ...\n", server_ip, port);
if (connect(client_fd, (struct sockaddr *)&server_addr,
sizeof(server_addr)) == -1) {
perror("connect");
} else {
printf("Connected!\n");
/* After connect, retrieve the local address the kernel assigned */
struct sockaddr_in local;
socklen_t len = sizeof(local);
getsockname(client_fd, (struct sockaddr *)&local, &len);
char local_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &local.sin_addr, local_ip, sizeof(local_ip));
printf("Local endpoint: %s:%u\n", local_ip, ntohs(local.sin_port));
}
close(client_fd);
}
int main(void)
{
server_example();
client_example("127.0.0.1", 8080);
return 0;
}
IPv6 addresses are 128 bits, written as 8 groups of 4 hexadecimal digits separated by colons:
Two shortening rules apply:
- Leading zeros within a group can be omitted:
0db8โdb8 - One run of consecutive all-zero groups can be replaced by
::
Important IPv6 special addresses:
::1โ loopback (equivalent to 127.0.0.1 in IPv4)::โ any address (equivalent to 0.0.0.0 in IPv4)fe80::/10โ link-local addresses (not routed beyond local link)
/* IPv6 socket example using AF_INET6 */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
/* AF_INET6 = IPv6, SOCK_STREAM = TCP */
int sockfd = socket(AF_INET6, SOCK_STREAM, 0);
if (sockfd == -1) { perror("socket"); return 1; }
struct sockaddr_in6 addr6;
memset(&addr6, 0, sizeof(addr6));
addr6.sin6_family = AF_INET6;
addr6.sin6_port = htons(8080);
/* Bind to :: (all IPv6 interfaces) */
addr6.sin6_addr = in6addr_any; /* equivalent of INADDR_ANY for IPv6 */
if (bind(sockfd, (struct sockaddr *)&addr6, sizeof(addr6)) == 0)
printf("Bound to [::]:8080 (all IPv6 interfaces)\n");
/* Convert IPv6 string to binary */
struct in6_addr target;
const char *ipv6_str = "2001:db8::1";
if (inet_pton(AF_INET6, ipv6_str, &target) == 1)
printf("Parsed %s successfully\n", ipv6_str);
/* Convert binary back to string โ needs INET6_ADDRSTRLEN (46 chars) */
char buf[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &target, buf, sizeof(buf));
printf("Back to string: %s\n", buf);
close(sockfd);
return 0;
}
htons() (host to network short) converts 16-bit port numbers; htonl() converts 32-bit IP addresses. Forgetting this is a classic bug causing silent connection failures.inet_aton() and inet_addr() are older IPv4-only functions. inet_addr() returns INADDR_NONE (0xFFFFFFFF) on error, which clashes with the valid broadcast address 255.255.255.255. inet_pton() (presentation to network) is the modern replacement โ it supports both IPv4 and IPv6, and returns 1 on success, 0 on invalid format, -1 on error. Always prefer inet_pton().โ Part 1: TCP/IP Layers โ Part 3: IP Fragmentation Next Chapter โ
