第3章 IP路由与三层转发
学习目标
- 理解IP地址结构和子网划分
- 掌握路由表的工作原理和配置
- 了解ICMP协议的作用和类型
- 能够配置静态路由和策略路由
🔬 原理
IP地址结构
IPv4地址格式:
格式:X.X.X.X(点分十进制)
示例:192.168.1.100
地址分类:
- A类:1.0.0.0 - 126.255.255.255(网络位8位)
- B类:128.0.0.0 - 191.255.255.255(网络位16位)
- C类:192.0.0.0 - 223.255.255.255(网络位24位)
- D类:224.0.0.0 - 239.255.255.255(组播)
- E类:240.0.0.0 - 255.255.255.255(保留)
特殊地址:
- 0.0.0.0:默认路由
- 127.0.0.1:回环地址
- 255.255.255.255:广播地址
- 169.254.x.x:链路本地地址
子网划分
CIDR表示法:
格式:IP地址/网络位长度
示例:192.168.1.0/24
子网掩码:
/24 = 255.255.255.0
/16 = 255.255.0.0
/8 = 255.0.0.0
子网计算:
网络地址 = IP地址 & 子网掩码
广播地址 = 网络地址 | (~子网掩码)
可用主机 = 2^(主机位) - 2
示例:
192.168.1.0/24:
- 网络地址:192.168.1.0
- 广播地址:192.168.1.255
- 可用主机:254个(192.168.1.1 - 192.168.1.254)
路由表原理
路由表结构:
目标网络 网关 接口 优先级 度量
192.168.1.0/24 * eth0 0 0
0.0.0.0/0 192.168.1.1 eth0 0 1
10.0.0.0/8 192.168.1.254 eth0 0 1
路由匹配原则:
- 最长前缀匹配:选择网络位最长的路由
- 优先级:数值越小优先级越高
- 度量值:相同优先级时选择度量值最小的
路由类型:
- 直连路由:接口配置IP后自动生成
- 静态路由:手动配置
- 动态路由:通过协议学习(BGP、OSPF等)
ICMP协议
ICMP(Internet Control Message Protocol):网络层控制协议
主要类型:
- Echo Request/Reply:ping命令使用
- Destination Unreachable:目标不可达
- Time Exceeded:TTL超时(traceroute使用)
- Redirect:路由重定向
ICMP头部:
类型(1) 代码(1) 校验和(2) 数据(变长)
常见类型码:
- 0:Echo Reply
- 3:Destination Unreachable
- 8:Echo Request
- 11:Time Exceeded
️ 实现
Linux路由表实现
路由表结构:
struct fib_table {
struct hlist_node tb_hlist;
u32 tb_id;
int tb_default;
int tb_num_default;
unsigned long tb_data[0];
};
struct fib_info {
struct hlist_node fib_hash;
struct hlist_node fib_lhash;
int fib_treeref;
atomic_t fib_clntref;
unsigned int fib_flags;
unsigned char fib_dead;
unsigned char fib_protocol;
unsigned char fib_scope;
unsigned char fib_type;
__be32 fib_prefsrc;
u32 fib_priority;
u32 fib_metrics[RTAX_MAX];
struct fib_nh fib_nh[0];
};
路由查找:
static int fib_table_lookup(struct fib_table *tb, const struct flowi4 *flp,
struct fib_result *res, int fib_flags)
{
struct trie *t = (struct trie *) tb->tb_data;
struct tnode *n;
struct fib_result res;
// 最长前缀匹配算法
n = trie_lookup(t, flp->daddr);
if (n) {
res = n->res;
return 0;
}
return -ENOENT;
}
策略路由实现
策略路由表:
struct fib_rule {
struct list_head list;
atomic_t refcnt;
int ifindex;
char ifname[IFNAMSIZ];
u32 mark;
u32 mark_mask;
u32 priority;
u32 flags;
u32 table;
u8 action;
u8 l3mdev;
struct fib_rule __rcu *next;
struct net *fr_net;
};
🛠️ 命令
基础路由命令
# 查看路由表
ip route show
# 查看特定路由
ip route get 8.8.8.8
# 添加静态路由
sudo ip route add 10.0.2.0/24 via 192.168.1.254
# 删除路由
sudo ip route del 10.0.2.0/24
# 添加默认路由
sudo ip route add default via 192.168.1.1
策略路由配置
# 查看策略路由表
ip rule show
# 添加策略路由
sudo ip rule add from 192.168.1.10 table 100
sudo ip route add default via 192.168.1.1 table 100
# 删除策略路由
sudo ip rule del from 192.168.1.10 table 100
ICMP相关命令
# ping测试
ping -c 3 8.8.8.8
# 路由追踪
traceroute 8.8.8.8
# 路径MTU发现
tracepath 8.8.8.8
# 检查ICMP统计
cat /proc/net/snmp | grep -i icmp
代码
简单路由表查看程序
// route_table.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <net/route.h>
#include <netinet/in.h>
int main() {
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("socket");
return 1;
}
char buffer[8192];
struct rt_msghdr *rtm;
struct sockaddr_in *sin;
// 读取路由表
int len = read(sock, buffer, sizeof(buffer));
if (len < 0) {
perror("read");
close(sock);
return 1;
}
rtm = (struct rt_msghdr *)buffer;
printf("Destination Gateway Flags Refs Use Interface\n");
printf("------------- ------------- ------ ---- --- ---------\n");
// 解析路由条目
while (len > 0) {
if (rtm->rtm_type == RTM_GET) {
sin = (struct sockaddr_in *)(rtm + 1);
char dst[INET_ADDRSTRLEN];
char gw[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &sin->sin_addr, dst, INET_ADDRSTRLEN);
if (rtm->rtm_flags & RTF_GATEWAY) {
sin = (struct sockaddr_in *)((char *)sin +
((sin->sin_len + 3) & ~3));
inet_ntop(AF_INET, &sin->sin_addr, gw, INET_ADDRSTRLEN);
} else {
strcpy(gw, "*");
}
printf("%-15s %-15s %-6x %-4d %-6d %s\n",
dst, gw, rtm->rtm_flags, rtm->rtm_refcnt,
rtm->rtm_use, "eth0");
}
len -= rtm->rtm_msglen;
rtm = (struct rt_msghdr *)((char *)rtm + rtm->rtm_msglen);
}
close(sock);
return 0;
}
编译运行:
gcc route_table.c -o route_table
./route_table
ICMP ping实现
// simple_ping.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
unsigned short checksum(void *b, int len) {
unsigned short *buf = b;
unsigned int sum = 0;
unsigned short result;
while (len > 1) {
sum += *buf++;
len -= 2;
}
if (len == 1) {
sum += *(unsigned char*)buf << 8;
}
while (sum >> 16) {
sum = (sum & 0xFFFF) + (sum >> 16);
}
result = ~sum;
return result;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s <host>\n", argv[0]);
return 1;
}
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sock < 0) {
perror("socket");
return 1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
inet_pton(AF_INET, argv[1], &addr.sin_addr);
struct icmp icmp_pkt;
icmp_pkt.icmp_type = ICMP_ECHO;
icmp_pkt.icmp_code = 0;
icmp_pkt.icmp_cksum = 0;
icmp_pkt.icmp_id = getpid();
icmp_pkt.icmp_seq = 1;
icmp_pkt.icmp_cksum = checksum(&icmp_pkt, sizeof(icmp_pkt));
if (sendto(sock, &icmp_pkt, sizeof(icmp_pkt), 0,
(struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("sendto");
close(sock);
return 1;
}
printf("PING %s: %d data bytes\n", argv[1], sizeof(icmp_pkt));
char buffer[1024];
struct sockaddr_in from;
socklen_t fromlen = sizeof(from);
if (recvfrom(sock, buffer, sizeof(buffer), 0,
(struct sockaddr*)&from, &fromlen) < 0) {
perror("recvfrom");
close(sock);
return 1;
}
struct iphdr *ip = (struct iphdr*)buffer;
struct icmp *icmp = (struct icmp*)(buffer + (ip->ihl << 2));
if (icmp->icmp_type == ICMP_ECHOREPLY) {
printf("64 bytes from %s: icmp_seq=1 time=0.001 ms\n", argv[1]);
}
close(sock);
return 0;
}
编译运行:
gcc simple_ping.c -o simple_ping
sudo ./simple_ping 8.8.8.8
🧪 实验
实验1:子网划分实践
目标:掌握子网划分和CIDR表示法
步骤:
# 1. 查看当前网络配置
ip addr show
# 2. 计算子网信息
# 假设网络:192.168.1.0/24
# 网络地址:192.168.1.0
# 广播地址:192.168.1.255
# 可用主机:192.168.1.1 - 192.168.1.254
# 3. 划分子网
# 将192.168.1.0/24划分为4个子网
# 子网1:192.168.1.0/26 (192.168.1.1-62)
# 子网2:192.168.1.64/26 (192.168.1.65-126)
# 子网3:192.168.1.128/26 (192.168.1.129-190)
# 子网4:192.168.1.192/26 (192.168.1.193-254)
# 4. 配置子网接口
sudo ip addr add 192.168.1.1/26 dev eth0
sudo ip addr add 192.168.1.65/26 dev eth0:1
sudo ip addr add 192.168.1.129/26 dev eth0:2
sudo ip addr add 192.168.1.193/26 dev eth0:3
# 5. 验证配置
ip addr show
预期结果:
- 理解子网划分原理
- 掌握CIDR计算方法
- 学会多IP配置
实验2:静态路由配置
目标:配置静态路由实现网络互通
步骤:
# 1. 查看当前路由表
ip route show
# 2. 添加静态路由
sudo ip route add 10.0.2.0/24 via 192.168.1.254
sudo ip route add 172.16.0.0/16 via 192.168.1.254
# 3. 验证路由
ip route get 10.0.2.5
ip route get 172.16.1.1
# 4. 测试连通性
ping -c 2 10.0.2.1
ping -c 2 172.16.1.1
# 5. 查看路由统计
cat /proc/net/route
预期结果:
- 理解路由表工作原理
- 掌握静态路由配置
- 验证路由选择过程
实验3:策略路由配置
目标:根据源地址选择不同路由
步骤:
# 1. 查看当前策略路由
ip rule show
# 2. 添加策略路由表
echo "200 custom" >> /etc/iproute2/rt_tables
# 3. 配置策略路由
sudo ip rule add from 192.168.1.10 table 200
sudo ip route add default via 192.168.1.1 table 200
# 4. 测试策略路由
sudo ip route add 192.168.1.10/32 dev lo
ping -I 192.168.1.10 8.8.8.8
# 5. 查看路由选择
ip route get 8.8.8.8 from 192.168.1.10
预期结果:
- 理解策略路由原理
- 掌握多路由表配置
- 验证路由选择策略
实验4:traceroute原理验证
目标:理解traceroute的工作原理
步骤:
# 1. 使用traceroute
traceroute -n 8.8.8.8
# 2. 手动实现traceroute
for i in {1..10}; do
echo "TTL $i:"
sudo hping3 -c 1 -V --ttl $i 8.8.8.8
echo
done
# 3. 抓包观察
sudo tcpdump -i any -nn 'icmp[0] == 11' &
traceroute 8.8.8.8
预期结果:
- 理解TTL递减机制
- 观察ICMP Time Exceeded
- 掌握网络路径发现
排错
常见问题排查
问题1:路由表混乱
# 清空路由表
sudo ip route flush table main
# 重新添加默认路由
sudo ip route add default via 192.168.1.1
# 检查路由优先级
ip route show table all
问题2:策略路由不生效
# 检查策略规则
ip rule show
# 检查路由表
ip route show table 200
# 验证源地址
ip addr show
问题3:ICMP被过滤
# 检查防火墙规则
sudo iptables -L -n | grep icmp
# 允许ICMP
sudo iptables -A INPUT -p icmp -j ACCEPT
# 检查ICMP统计
cat /proc/net/snmp | grep -i icmp
排错清单
- [ ] 检查IP地址配置(地址、掩码、广播)
- [ ] 验证路由表(直连、静态、默认)
- [ ] 确认网关可达性(ping网关)
- [ ] 检查策略路由(规则、表、优先级)
- [ ] 测试ICMP功能(ping、traceroute)
- [ ] 查看路由统计(/proc/net/route)
- [ ] 检查系统日志(dmesg、/var/log/syslog)