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 种类型:

1.​默认路由(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


​2.主机路由(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


3.​网络路由(特定的网段,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.c

int 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.c

struct rtable *__ip_route_output_key(struct net *net, struct flowi4 *fl4)
{
 ...
 // 进入 fib_lookup
 if (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_lookup
 err = 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;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值