1. Vai trò của Địa chỉ IP trong Mạng
Trong môi trường Internet, mỗi thiết bị tham gia giao tiếp đều cần một định danh duy nhất để hệ thống phân phối dữ liệu chính xác. Địa chỉ IP (Internet Protocol Address) đóng vai trò là "mã nhận dạng" logic này.
Địa chỉ IP thực hiện hai nhiệm vụ cốt lõi:
- Định danh nguồn: Cho phép thiết bị nhận biết gói tin đến từ đâu để phản hồi lại.
- Định danh đích: Giúp các bộ định tuyến (router) quyết định đường đi tiếp theo của gói tin dựa trên địa chỉ mục tiêu.
Khác với số điện thoại—nơi người dùng cần ghi nhớ số của người khác—địa chỉ IP thường được người dùng cuối ẩn giấu bởi hệ thống phân giải tên miền (DNS). Khi người dùng truy cập www.example.com, DNS sẽ chuyển đổi tên dễ đọc này thành địa chỉ IP số (ví dụ: 93.184.216.34) để máy chủ thực hiện kết nối. Tuy nhiên, việc hiểu và thao tác trực tiếp với địa chỉ IP là cần thiết khi cấu hình mạng cục bộ hoặc khắc phục sự cố DNS.
2. Hệ thống Phân bổ và Khả năng Mở rộng
Tránh xung đột địa chỉ là yêu cầu bắt buộc, vì hai thiết bị không thể dùng cùng một địa chỉ IP công cộng. Để quản lý hàng tỷ thiết bị trên toàn cầu, địa chỉ IP được phân bổ theo mô hình phân cấp (Hierarchy):
- IANA (Cơ quan cấp phát số hiệu Internet): Quản lý kho địa chỉ toàn cầu và phân chia cho các tổ chức cấp vùng.
- RIR (Tổ chức đăng ký Internet khu vực): Tiếp nhận khối địa chỉ lớn và cung cấp cho các nhà cung cấp dịch vụ Internet (ISP).
- ISP: Cấp phát địa chỉ cho doanh nghiệp và người dùng cuối.
Cấu trúc phân cấp này cho phép áp dụng kỹ thuật Tổng hợp định tuyến (Route Aggregation). Thay vì lưu trữ thông tin định tuyến cho từng địa chỉ IP riêng lẻ (dẫn đến việc bộ nhớ router bị quá tải), các router chỉ cần lưu trữ các khối địa chỉ lớn (ví dụ: 203.0.0.0/8 trỏ về một ISP cụ thể). Điều này giúp bảng định tuyến luôn gọn nhẹ, đảm bảo Internet có thể mở rộng.
3. Thao tác Cơ bản với Địa chỉ IP (C++)
Đoạn mã dưới đây minh họa cách xử lý địa chỉ IPv4 trong C++. Thay vì các hàm rời rạc, mã nguồn sử dụng cấu trúc dữ liệu để đóng gói logic chuyển đổi giữa chuỗi (string) và giá trị nhị phân (binary), đồng thời hỗ trợ hiển thị dưới dạng nhị phân.
#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
#include <vector>
#include <stdexcept>
#include <arpa/inet.h>
// Cấu trúc đại diện cho một địa chỉ IPv4
struct IPv4_Address {
uint32_t raw_value; // Giá trị nhị phân 32-bit
// Hàm tĩnh để chuyển đổi từ chuỗi sang số nguyên
static IPv4_Address from_string(const std::string& ip_str) {
IPv4_Address result;
struct in_addr addr;
if (inet_pton(AF_INET, ip_str.c_str(), &addr) != 1) {
throw std::invalid_argument("Định dạng địa chỉ IPv4 không hợp lệ: " + ip_str);
}
// Chuyển từ Network Byte Order sang Host Byte Order
result.raw_value = ntohl(addr.s_addr);
return result;
}
// Chuyển đổi ngược lại từ số nguyên sang chuỗi
std::string to_string() const {
struct in_addr addr;
addr.s_addr = htonl(raw_value);
char buffer[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &addr, buffer, sizeof(buffer)) == nullptr) {
return "Lỗi chuyển đổi";
}
return std::string(buffer);
}
// Hiển thị dưới dạng chuỗi nhị phân 32 bit
std::string to_binary() const {
std::ostringstream oss;
for (int i = 31; i >= 0; --i) {
oss << ((raw_value >> i) & 1);
if (i > 0 && i % 8 == 0) oss << ".";
}
return oss.str();
}
};
int main() {
std::vector<std::string> test_ips = {"192.168.1.1", "10.0.0.255", "203.0.113.50"};
std::cout << "|-- Kiểm thử Địa chỉ IPv4 --|\n";
for (const auto& ip_str : test_ips) {
try {
IPv4_Address ip = IPv4_Address::from_string(ip_str);
std::cout << "Địa chỉ: " << ip_str << "\n";
std::cout << " Nhị phân: " << ip.to_binary() << "\n\n";
} catch (const std::exception& e) {
std::cerr << "Lỗi: " << e.what() << "\n";
}
}
return 0;
}
4. Cấu trúc Địa chỉ IPv4 và IPv6
IPv4 sử dụng địa chỉ 32-bit, được biểu diễn dưới dạng Dotted-Decimal (4 số thập phân cách nhau bởi dấu chấm). Mỗi octet có giá trị từ 0 đến 255. Tuy nhiên, sự cạn kiệt địa chỉ IPv4 đã dẫn đến sự ra đời của IPv6 với độ dài 128-bit.
Đặc điểm của IPv6:
- Cú pháp: Gồm 8 nhóm, mỗi nhóm 16-bit (4 chữ số thập lục phân), cách nhau bởi dấu hai chấm (ví dụ:
2001:0db8:85a3:0000:0000:8a2e:0370:7334). - Tối ưu hóa ký pháp (RFC 4291):
- Bỏ các số 0 đứng đầu trong mỗi nhóm (ví dụ:
0db8thànhdb8). - Sử dụng
::để thay thế cho chuỗi các nhóm 0 liên tiếp (chỉ được dùng một lần trong một địa chỉ).
- Bỏ các số 0 đứng đầu trong mỗi nhóm (ví dụ:
- Trong URL: Để tránh nhầm lẫn với dấu hai chấm của số cổng (port), địa chỉ IPv6 trong URL phải được bao bởi dấu ngoặc vuông, ví dụ:
http://[2001:db8::1]:8080/.
5. Phân loại Địa chỉ Theo Lớp (Classful Addressing)
Trước khi CIDR ra đời, Internet sử dụng cơ chế "Classful Addressing" chia địa chỉ 32-bit thành các lớp dựa trên các bit đầu tiên (prefix bits):
- Class A: Bắt đầu bằng bit
0. Dành cho mạng lớn (mạng A có ~16 triệu host).- Bit đầu: 0
- Network ID: 8 bit (bit đầu + 7 bit tiếp theo)
- Host ID: 24 bit
- Class B: Bắt đầu bằng
10. Dành cho mạng vừa và lớn.- Bit đầu: 10
- Network ID: 16 bit
- Host ID: 16 bit (~65 nghìn host)
- Class C: Bắt đầu bằng
110. Dành cho mạng nhỏ.- Bit đầu: 110
- Network ID: 24 bit
- Host ID: 8 bit (254 host)
- Class D: Bắt đầu bằng
1110. Dành cho Multicast (Gửi đến nhóm). - Class E: Bắt đầu bằng
1111. Dành cho mục đích thử nghiệm/nghiên cứu (Reserved).
Lưu ý: Trong mỗi mạng, địa chỉ có Host ID toàn 0 là địa chỉ mạng (Network Address), và địa chỉ có Host ID toàn 1 là địa chỉ quảng bá (Broadcast Address). Do đó, số host khả dụng là $2^{\text{host\_bits}} - 2$.
Mô hình này đã bộc lộ nhược điểm nghiêm trọng: Sự lãng phí địa chỉ. Một tổ chức cần 1.000 địa chỉ không thể dùng Class C (chỉ 254 host) mà phải dùng Class B (dù thực tế chỉ dùng chưa đến 2% dung lượng). Việc phân chia cứng nhắc này dẫn đến cạn kiệt Class B và sự phát triển của subnetting.
6. Cơ chế Chia mạng con (Subnetting)
Subnetting là kỹ thuật "mượn" các bit từ phần Host ID để tạo thành phần Subnet ID, cho phép một mạng lớn được chia nhỏ thành các mạng con độc lập về mặt logic nhưng vẫn thuộc cùng một khối địa chỉ mạng lớn.
Cấu trúc địa chỉ lúc này gồm 3 phần: Network ID | Subnet ID | Host ID. Ranh giới giữa các phần này được xác định bởi Subnet Mask (Mặt nạ mạng con).
Ví dụ: Nếu một mạng Class B (mặc định 16 bit network, 16 bit host) dùng mask 255.255.255.0 (hay /24):
- Network ID: 16 bit đầu tiên.
- Subnet ID: 8 bit tiếp theo (mượn từ Host).
- Host ID: 8 bit cuối cùng.
Việc này tạo ra $2^8 = 256$ mạng con, mỗi mạng con có $2^8 - 2 = 254$ host.
7. Tính toán Mạng con (C++)
Đoạn mã dưới đây thực hiện phép toán bitwise để xác định địa chỉ mạng, địa chỉ quảng bá, và dãy địa chỉ host khả dụng từ một địa chỉ IP và mặt nạ mạng cụ thể.
#include <iostream>
#include <string>
#include <vector>
#include <arpa/inet.h>
struct SubnetInfo {
std::string ip_str;
std::string mask_str;
std::string network_addr;
std::string broadcast_addr;
std::string first_host;
std::string last_host;
uint32_t host_count;
};
// Hàm trợ giúp chuyển đổi chuỗi IP sang số nguyên không dấu
uint32_t parse_ip(const std::string& ip_str) {
in_addr addr;
if (inet_pton(AF_INET, ip_str.c_str(), &addr) != 1) {
throw std::runtime_error("Định dạng IP không hợp lệ");
}
return ntohl(addr.s_addr);
}
// Hàm trợ giúp chuyển đổi số nguyên sang chuỗi IP
std::string format_ip(uint32_t ip_val) {
in_addr addr;
addr.s_addr = htonl(ip_val);
char buffer[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &addr, buffer, sizeof(buffer));
return std::string(buffer);
}
SubnetInfo analyze_subnet(const std::string& ip, const std::string& mask) {
uint32_t ip_val = parse_ip(ip);
uint32_t mask_val = parse_ip(mask);
// Địa chỉ mạng = IP AND Mask
uint32_t network_val = ip_val & mask_val;
// Địa chỉ quảng bá = Network OR (NOT Mask)
uint32_t broadcast_val = network_val | (~mask_val);
SubnetInfo info;
info.ip_str = ip;
info.mask_str = mask;
info.network_addr = format_ip(network_val);
info.broadcast_addr = format_ip(broadcast_val);
// Dãy host khả dụng bắt đầu từ mạng + 1 đến quảng bá - 1
info.first_host = format_ip(network_val + 1);
info.last_host = format_ip(broadcast_val - 1);
// Tính số lượng host
info.host_count = broadcast_val - network_val - 1;
return info;
}
int main() {
// Kiểm tra địa chỉ 128.32.1.5 với mặt nạ 255.255.255.0
try {
SubnetInfo result = analyze_subnet("128.32.1.5", "255.255.255.0");
std::cout << "=== Kết quả Phân tích Mạng con ===\n";
std::cout << "IP gốc: " << result.ip_str << "\n";
std::cout << "Mặt nạ: " << result.mask_str << "\n";
std::cout << "Network ID: " << result.network_addr << "\n";
std::cout << "Broadcast: " << result.broadcast_addr << "\n";
std::cout << "Host đầu: " << result.first_host << "\n";
std::cout << "Host cuối: " << result.last_host << "\n";
std::cout << "Số Host: " << result.host_count << "\n";
} catch (const std::exception& e) {
std::cerr << "Lỗi xử lý: " << e.what() << "\n";
}
return 0;
}