Linux Kernel TCP/IP Stack — L3 Layer — 路由器子系统
目录
文章目录
- 目录
- Linux 作为一个路由器
- 路由表项的类型
- route 指令
- ip route 指令
- 添加默认路由
- 添加静态路由
- 删除静态路由
- 操作示例
- Linux Kernel 路由子系统
- 路由表
- 发送数据时进行路由选择(选路)
- 接收数据时进行路由选择(选路)
- 路由查找
Linux 作为一个路由器
Linux 作为一个路由器只需要打开 ipv4.forward 特性即可。
$ vim /etc/sysctl.conf
...
net.ipv4.ip_forward=1$ sysctl -p# 开启 NAT
$ iptables -t nat -A POSTROUTING -j MASQUERADE
开启了路由功能之后的 Linux 服务器就相当于一个 Router,Linux 服务器的路由表就相当于 Router 的路由表,Linux 服务器上的网卡就相当于 Router Interface。数据包会根据路由表规则在这些 “网卡” 中选择下一跳。
同时要注意 iptables 规则是否可能会造成混乱,对于 Router 所有数据包都是 SNAT,所以不需要配置 DNAT 规则,建议清理干净。
路由表项的类型
根据子网掩码的特征,可以将路由分为 3 种类型:
- 默认路由(0.0.0.0):当主机不能在路由表中查找到目标主机的 IP 地址或网络路由记录时,数据包就被发送到默认路由(默认网关)上。Flags 字段为 G。e.g. 默认路由是 IP 地址为 192.168.1.1 的路由器。
Destination Gateway Genmask Flags Metric Ref Use Iface
----------- ------- ------- ----- ------ --- --- -----
default 192.168.1.1 0.0.0.0 UG 0 0 0 eth0
- 主机路由(255.255.255.255):是路由表项中指向单个 IP 地址或主机名的记录。Flags 字段为 H。e.g. 本地主机通过 IP 地址 192.168.1.1 的路由器到达 IP 地址为 10.0.0.10 的主机。
Destination Gateway Genmask Flags Metric Ref Use Iface
----------- ------- ------- ----- ------ --- --- -----
10.0.0.10 192.168.1.1 255.255.255.255 UH 0 0 0 eth0
- 网络路由(特定的网段,e.g. 255.255.255.0):代表主机可以到达的网络。Flags 字段为 N。e.g. 本地主机将发送到网络 192.19.12 的数据包转发到 IP 地址的 192.168.1.1 的路由器。
Destination Gateway Genmask Flags Metric Ref Use Iface
----------- ------- ------- ----- ----- --- --- -----
192.19.12 192.168.1.1 255.255.255.0 UN 0 0 0 eth0
route 指令
命令行下输入 route -n
或 netstat -rn
,就可以打印本机的 main 路由表:
$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.18.22.1 0.0.0.0 UG 0 0 0 br-ex
172.18.22.0 0.0.0.0 255.255.255.0 U 0 0 0 br-ex
192.168.0.0 0.0.0.0 255.255.255.0 U 0 0 0 o-hm0
192.168.122.0 0.0.0.0 255.255.255.0 U 0 0 0 virbr0
字段 | 说明 |
---|---|
Destination | 目的网段(或目的主机),0.0.0.0(或 default)表示默认路由 |
Gateway |
网关地址,0.0.0.0 或 * 表示当前路由记录对应的 Destination 跟本机处于同一个网段,通信时不需要经过网关
|
Genmask | 地址掩码,当路由项为默认路由时会设置为 0.0.0.0,当路由项为主机路由时会设置为 255.255.255.255 |
Flags | 标志,含义参考表格后面的解释 |
Metric | 跃点,到达指定网络所需的中转数(不在 Linux Kernel 中使用) |
Ref | 引用,路由项引用次数(不在 Linux Kernel 中使用) |
Use | 使用,路由项被路由软件查找的次数(不在 Linux Kernel 中使用) |
Iface | 接口 |
Flags 含义:
- U - 有效路由
- H - 主机路由
- G - 网关路由
- N - 网段路由
- R - 恢复动态路由产生的表项
- D - 由路由的后台程序动态地安装
- M - 由路由的后台程序修改
- ! - 拒绝路由
ip route 指令
使用 ip route show
查看 main 路由表:
$ ip route show | column -t
default via 172.18.22.1 dev br-ex
172.18.22.0/24 dev br-ex proto kernel scope link src 172.18.22.200
192.168.0.0/24 dev o-hm0 proto kernel scope link src 192.168.0.96
192.168.122.0/24 dev virbr0 proto kernel scope link src 192.168.122.1
- 默认路由,没有匹配路由项的数据包全部通过设备 br-ex 转发至网关 172.18.22.1
default via 172.18.22.1 dev br-ex
- 目的网段为 172.18.22.0/24 的数据包通过设备 br-ex 转发,该路由项由内核自动创建,直连,数据包源 IP 为 172.18.22.200,不需要经过网关(主机就处于该网段)。
172.18.22.0/24 dev br-ex proto kernel scope link src 172.18.22.200
- 目的网段为 10.15.150.0/24 的数据包通过设备 enp0s3 转发至网关 192.168.150.253,该路由项为 static 静态路由,由手动创建,到达指定网络需要的转发的跳数为 1。
10.15.150.0/24 via 192.168.150.253 dev enp0s3 proto static metric 1
添加默认路由
ip route add default via 172.18.22.1 dev br-ex
NOTE:当 Linux 上物理网卡(e.g. eth0)挂载到了 Linux Bridge(e.g. br-ex)上之后,默认路由的设备应该为 Bridge 设备,因为此时的物理网卡 IP 无效了。
添加静态路由
ip route add [目的网段] via [网关] dev [接口] [table <table_number>]
e.g. 目标网段为 10.15.150.0/24 的数据包通过接口 enp0s3 转发到网关 192.168.150.253。
ip route add 10.15.150.0/24 via 192.168.150.253 dev enp0s3
删除静态路由
ip route del [目标网段]
e.g.
ip route del 10.15.150.0/24
操作示例
要实现全网通信,也就是网络中任意两个节点都可以通信,就要求每个路由器的路由表中必须有到所有网段的路由。
对于路由器来说,它只知道与自己直接相连的网段,对于没有直连的网络,则需要人工添加到这些网段的路由。如 R1 路由器直接与 A、B 两个网段相连,R1 会自动生成 A、B 网段的路由表,所以 R1 天然就知道数据包如何在在 A、B 网段之间进行转发。但 R1 没有与直接 C、D 网段相连,A、B 网段发出的数据包无法被 R1 转发至 C、D 网段,此时就需要手动添加 C、D 网段的路由到 R1(如上图所示)。此时 A 网段(192.168.0.2/24)访问 C 网段(172.16.1.2/24),R1 就知道该数据包应该被转发至 R2 的网关 172.16.0.2/24,R2 在根据目标地址(172.16.1.2/24)转发至 R3。
“下一跳” 指的是数据包下一步转发给哪个路由器,应该为目标路由器的入口地址。对于点到点链路而言,“下一跳” 地址可以被写成目标网络的出口(如 serial 2/0),因为 PPP 协议,数据帧从一端发出,接收端只有一个;但对于路由器而言,因为路由器之间是一个以太网连接,这种情况下添加路由器,只能写下一跳地址,而不能写路由器的出口,因为路由器的出口连的是以太网,它无法知道该将数据包发给该以太网中的哪个路由器。
路由器只关心到某个 网段 如何转发数据包,所以添加路由时一定要是某个网段的地址,而不能是某个特定地址的路由,即一定要确保 IP 地址的主机位全是 0。如果要让路由器转发到一个 IP 地址的路由,子网掩码要写成 4 个 255。如:
R1(config)#ip route 192.168.1.3 255.255.255.255 172.16.0.2
。
简单来说,每个 Router 都会连接多个 LAN,如果当前 Router 没有这个 LAN,那么 Router 就应该将数据包 下一跳 到连接这个 LAN 的 Router 上,之间可能隔着多个 Routers。
NOTE:需要注意的是,路由器功能生效的前提是连接一个 LAN 的路由网关接口必须配置了同一网段的网关 IP 地址。笔者经历过使用 DPDK 用户态数据面转发软件的场景,就是因为没有配置网管 IP 所以没有触发数据包的转发动作。无论是传统的路由交换组网场景,还是基于用户态内核旁路技术的自定义网络的组网场景,路由网关都是必要需要存在的。
Linux Kernel 路由子系统
Linux 在发送数据时和接收数据时都会涉及到路由子系统的工作。
路由表
Routing Table(路由表)在 Kernel 中的另外一个别名是 FIB(Forwarding Information Base,转发信息库)。所以,在 Kernel 中看到的 fib 开头的定义基本上就是和路由表相关的功能。
在默认情况下,Linux 只有 local 和 main 两个路由表,如果内核编译时支持策略路由,那么最多可以配置 255 个独立的路由表。查看是否开启了 CONFIG_IP_MULTIPLE_TABLES 多路由表支持。
$ cat /boot/config-3.10.0-693.el7.x86_64
...
CONFIG_IP_MULTIPLE_TABLES=y
若开启了,此时 Linux 自身会维护 4 个路由表:
$ cat /etc/iproute2/rt_tables
#
# reserved values
#
255 local
254 main
253 default
0 unspec
#
# local
#
#1 inr.ruhep
- 0 unspec table: 系统保留表。
- 253 defulte table:没特别指定的默认路由都放在该表。
- 254 main table:没指明路由表的所有路由都放在该表。
- 255 locale table:保存本地接口地址,广播地址、NAT 地址。该由系统维护,用户不得更改。
查看指定的路由表项目:
$ ip route list table {table_number}
# e.g.
$ ip route list table local
同时,Linux 支持 Network namespace,如果存在多个 Network namespace 的话,就会存在多套路由表。路由表在整个内核数据结构中的关联关系如下图所示。
发送数据时进行路由选择(选路)
Linux 在发送数据包的时候需要进行路由选路,因为服务器上可能会存在多张网卡设备。当待发送的数据包进入 IP 层的路由子系统时,就要进行路由选择(选路),以决定使用哪张网卡设备把数据包送出去。
IP 网络层数据包发送的入口函数是 ip_queue_xmit(),其中调用的 ip_route_output_ports() 函数实现了路由项的查找,继而完成路由选择,在路由表中进行匹配,然后决定使用哪个网卡发送出去。
// net/ipv4/ip_output.cint ip_queue_xmit(struct sk_buff *skb, struct flowi *fl)
{// 路由选择过程// 选择完后记录路由信息到 skb 上rt = (struct rtable *)__sk_dst_check(sk, 0);if (rt == NULL) {// 没有缓存则查找路由项rt = ip_route_output_ports(...);sk_setup_caps(sk, &rt->dst);}skb_dst_set_noref(skb, &rt->dst);...//发送ip_local_out(skb);
}
接收数据时进行路由选择(选路)
因为 Linux 提供了路由器的功能,可以像路由器一样工作,将接收到的数据包通过合适的网卡将其转发出去。
IP 网络层数据包接收的入口函数是 ip_rcv(),执行后调用到 ip_rcv_finish(),在这里展开路由选择。
如果匹配确实就是本设备的网络包,那么就通过 ip_local_deliver() 送到更上层的 TCP 层进行处理。
如果匹配发现是非本设备的网络包,那就进入到 ip_forward() 进行转发,最后通过 ip_output() 发送出去。
// net/ipv4/ip_input.c
static int ip_rcv_finish(struct sk_buff *skb){...if (!skb_dst(skb)) {int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,iph->tos, skb->dev);...}...return dst_input(skb);
}// net/ipv4/route.c
// 进行路由查找
int ip_route_input_noref(struct sk_buff *skb, __be32 daddr, __be32 saddr,u8 tos, struct net_device *dev)
{...res = ip_route_input_slow(skb, daddr, saddr, tos, dev);return res;
}
路由查找
上述内容提到,数据包发送过程中调用了 ip_route_output_ports() 来查找路由,数据包接收过程中调用了 ip_route_input_slow() 来查找。其实这两个函数最终都会调用到 fib_lookup() 这个核心函数。
fib_lookup() 函数依次到 local 和 main 表中进行匹配,匹配到后就返回。并且 local 表的优先级要高于 main 表,如果 local 表中找到了规则,则路由过程就结束了。
根据这个实现,当我们 ping 本地 IP 地址的时,eth0 上是抓不到包的。因为所有命中 local 表的包,都会被送往 loopback 设备,而不是 eth0。
// net/ipv4/route.cstruct rtable *__ip_route_output_key(struct net *net, struct flowi4 *fl4)
{...// 进入 fib_lookupif (fib_lookup(net, fl4, &res)) {}
}// net/ipv4/route.c
static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr,u8 tos, struct net_device *dev)
{...// 进入 fib_lookuperr = fib_lookup(net, &fl4, &res);
}// include/net/ip_fib.h
static inline int fib_lookup(struct net *net, const struct flowi4 *flp,struct fib_result *res)
{struct fib_table *table;table = fib_get_table(net, RT_TABLE_LOCAL);if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF))return 0;table = fib_get_table(net, RT_TABLE_MAIN);if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF))return 0;return -ENETUNREACH;
}
Linux Kernel TCP/IP Stack — L3 Layer — 路由器子系统相关推荐
- Linux Kernel TCP/IP Stack — L3 Layer — netfilter 框架
目录 文章目录 目录 netfilter 框架 netfilter 的组成模块 netfilter 的 Hook 机制实现 netfilter 的工作原理 规则(Rules) 链(Chains) 表( ...
- Linux Kernel TCP/IP Stack — L3 Layer — netfilter 框架 — iptables NAPT 网络地址/端口转换
目录 文章目录 目录 网络地址转换(NAT) SNAT DNAT 端口映射(PAT) 网络地址转换(NAT) IP 网络有公网与私网的区分,通常内网使用私网 IP,Internet 使用公网 IP,而 ...
- Linux Kernel TCP/IP Stack — L3 Layer
目录 文章目录 目录 L3 Layer 框架 IP Handler L3 Layer 框架 IP Handler 实现了以下功能: IP 数据包的分片和重组. IP 协议的字段处理,例如:IP 选项, ...
- Linux Kernel TCP/IP Stack — L3 Layer — netfilter/iptables 防火墙
目录 文章目录 目录 iptables/netfilter 框架 iptables-service iptables 指令应用 查看规则 添加规则 删除规则 修改规则 保存和加载规则 常规初始化配置 ...
- Linux Kernel TCP/IP Stack — L3 Layer — netfilter 框架 — conntrack(CT,连接跟踪)
目录 文章目录 目录 CT CT CT(conntrack,connection tracking,连接跟踪),顾名思义,就是跟踪(并记录)连接的状态,是许多网络应用的基础.例如:iptables.L ...
- Linux Kernel TCP/IP Stack — L3 Layer — 邻居发现子系统
目录 文章目录 目录 邻居发现子系统 邻居发现子系统 提供以下功能: ARP 协议(IPV4) NDP 协议(IPV6) 邻居表新建,更新,老化机制,状态转化等.
- Linux Kernel TCP/IP Stack — L2 Layer — Linux Bridge(虚拟网桥)的基本操作
目录 文章目录 目录 Linux bridge 的基本操作 创建 Bridge 将 veth pair 连上 Bridge 为 Bridge 配置 IP 地址 将物理网卡接口设备挂靠 Bridge L ...
- Linux Kernel TCP/IP Stack — L7 Layer — Application Socket I/O 接口类型
目录 文章目录 目录 基本概念 同步与异步 阻塞与非阻塞 I/O 操作的执行流程 Socket I/O 接口类型 阻塞 IO 缺点 非阻塞 IO 缺点 阻塞 IO 与非阻塞 IO 的区别 IO 多路复 ...
- Linux Kernel TCP/IP Stack — L1 Layer — 多队列网卡
目录 文章目录 目录 多队列网卡 Intel 82575 的多队列硬件实现 Intel 82575 的多队列软件驱动实现 多队列网卡识别 多队列网卡 多队列网卡,是一种用来解决网络 I/O QoS 问 ...
最新文章
- ACM寒假训练第二周总结
- python getattr_Python中的getattr()函数详解
- Oracle 10g中对resumable session的增强
- JAVA标识符的命名规则和规范
- 抵御物联网DDoS军团
- P4091-[HEOI2016/TJOI2016]求和【斯特林数,NTT】
- Linux-正则表达式的POSIX规范及流派
- 【转载】H264编码原理以及I帧、B帧、P帧
- Log4jdbc-log4j2打印mybatis语句
- 存储大师新作,三星存储四大新品面世!
- 关于FPGA软件quartus仿真出现cannot launch the modelsim software问题的解决
- 雷达存在感应器技术,实时感知控制应用,雷达人体探测方案
- Himall商城普通帮助类(一)
- ubuntu20.04.1下安装qt4相关依赖库
- 前端开发:Mac电脑修改hosts文件的方法
- 如何换ionic里面的图标
- redis连接出错 ERR AUTH <password> called without any password configured for the default user.
- IDC机房动力环境设备维护
- 一个模仿水滴筹的项目(只是一个半成品)
- C++20中的协程(Coroutine)
热门文章
- 分享Kali Linux 2016.2第49周镜像文件
- Xamarin基础命名空间Microsoft.SqlServer.Server
- jira 审批流程_博兴县行政审批服务局推暖心服务工程 企业开办实现“全程网办”_博兴新闻...
- python6翻_洗礼灵魂,修炼python(6)--活起来的代码+列表
- java01背包问题算法_经典动态规划--01背包问题
- 深度神经网络对基于EEG的情绪识别的关键频带和通道的研究
- 没听说过“羽毛球VR”计划?嗯?
- c语言从键盘输入千米数,第二章 C语言编程基础.ppt
- 阿里达摩院百万大奖评选开启!这次人人都能给青年科学家当伯乐
- 科学家们竟用乐高观察细胞,网友:万万没想到啊