0%

ip 命令概览

iproute2 是 Linux 系统上提供的网络管理套件,用于取代之前 Linux 系统上提供的网络管理命令(例如 ifconfig、arp、route 等命令)。其主要包括 ip、tc 以及其他一些命令。虽然为了向后兼容,Linux 系统仍然提供了 ifconfig 等命令,但是这些命令已经不支持新的特性,例如 network namespace 等,甚至很多新 Linux 发行版本已经不再包含这些陈旧的网络管理命令了。这篇文章将学习 iproute2 中 ip 命令的使用。

其实 iproute2 早在 2000 年就已经存在了,但是其文档比较少,导致很多人并不怎么使用这些新的命令。iproute2 能够让你对 Linux 的网络特性有更多的控制手段,同时也能丰富你的调试工具箱。

说明

IP 命令的格式为 ip [ OPTIONS ] OBJECT { COMMAND | help }。其中,OBJECT 为 link、address、route 等等,而不同 OBJECT 能够使用的 OPTIONS 和 COMMAND 也有所不同。这里需要强调,OPTIONS 、OBJECT、COMMAND 之间的顺序是不能改变的,否则会出现命令解析错误。

ip 命令中的很多部分可以简写,但是本文统一使用命令的标准形式,这方便于理解与记忆。另外,很多 ip 配置命令需要 root 权限。

iproute2 提供了不同选项,以支持格式化其输出内容:

  • -o (–oneline):用反斜线取代每一个换行符
  • -br (–brief):产生简洁的、面向机器的输出内容,以便对输出使用 awk/sed 等文本解析命令
  • -j (json):产生 JSON 格式的输出。其支持使用 –pretty 以及 –brief 选项来改变格式以及输出的详细程度

地址管理

ip address 用于管理 IP 地址。

显示 IP 地址

  • 显示所有接口 IP 地址,可以使用 -4 或 -6 选项来只显示 IPv4 或 IPv6 地址
1
2
3
ip address show
ip -4 address show
ip -6 address show
  • 显示指定接口的 IP 地址
1
ip address show ${interface name}

示例:

1
ip address show lo
  • 只显示运行的接口的 IP 地址
1
ip address show up
  • 只显示静态或动态配置的地址

只显示静态配置的地址:

1
ip address show [dev ${interface}] permanent

只显示动态分配的地址:

1
ip address show [dev ${interface}] dynamic

示例:

1
2
3
4
5
6
7
8
9
10
11
$ ip address show dev ens32 permanent
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:40:81:a0 brd ff:ff:ff:ff:ff:ff
inet6 fe80::b8f:2cae:5d8c:7d1b/64 scope link
valid_lft forever preferred_lft forever

$ ip address show dev ens32 dynamic
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:40:81:a0 brd ff:ff:ff:ff:ff:ff
inet 192.168.204.145/24 brd 192.168.204.255 scope global dynamic ens32
valid_lft 1092sec preferred_lft 1092sec

添加 IP 地址

使用如下命令向指定接口添加 IP 地址,这里 ${address} 使用点分十进制格式指定,${mask} 可以使用点分十进制格式,也可以使用前缀长度格式。

1
ip address add ${address}/${mask} dev ${interface name}

示例:

1
2
3
4
5
6
7
8
9
10
$ sudo ip address add 127.0.0.2/255.0.0.0 dev lo
$ sudo ip address add 127.0.0.3/8 dev lo
$ ip -4 address show lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet 127.0.0.2/8 scope host secondary lo
valid_lft forever preferred_lft forever
inet 127.0.0.3/8 scope host secondary lo
valid_lft forever preferred_lft forever

可以在一个接口上添加多个 IP 地址,目的地址为这些 IP 地址的 IP 报文都会被系统接受。而对于从该接口上发出去的 IP 报文,默认将使用该接口上配置的第一个 IP 地址,也被称 primary IP 地址,其他的 IP 地址则被称为 secondary IP 地址。

另外,使用如下命令为 IPv4 地址添加可读的描述信息:

1
ip address add ${address}/${mask} dev ${interface name} label ${interface name}:${description}

示例:

1
2
3
$ sudo ip address add 127.0.0.4/8 dev lo label lo:LoopbackInterface
RTNETLINK answers: Numerical result out of range
$ sudo ip address add 127.0.0.4/8 dev lo label lo:LoopbackIP

label 必须以接口名作为起始,之后使用 : 隔开描述信息。整个 label 字符串不能超过 16 个字符,否则会出现错误提示。另外,对于 IPv6 地址,该命令的 label 子命令没有效果。也就是说只会添加 IPv6 地址,无法设置 label 描述信息。

删除地址

使用如下命令,从接口上删除一个 IP 地址:

1
ip address delete ${address}/${prefix} dev ${interface name}

示例:

1
2
3
4
5
~$ sudo ip address delete 127.0.0.4/8 dev lo
~$ sudo ip address delete 127.0.0.3/8 dev lo
~$ sudo ip address delete 127.0.0.2/8 dev lo
~$ ip --brief -4 address show lo
lo UNKNOWN 127.0.0.1/8

需要注意,interface 是必须指定的,因为 Linux 允许在多个接口上配置同一个 IP 地址。

删除所有地址

使用如下命令删除一个接口下的所有 IP 地址:

1
ip address flush dev ${interface name}

示例:

1
2
3
4
5
$ ip --brief address show lo
lo UNKNOWN 127.0.0.1/8 ::1/128
$ sudo ip -6 address flush dev lo
$ ip --brief address show lo
lo UNKNOWN 127.0.0.1/8

默认情况下该命令移除该接口下所有的 IPv4 和 IPv6 地址,如果你想仅移除 IPv4 或 IPv6 地址,可以使用 -4 或 -6 选项。

注意事项

没有办法交换接口上的 primary 地址和 secondary 地址,因此总是应该首先设置 primary 地址。但是如果 sysctl 变量 net.ipv4.conf.${interface}.promote_secondaries 被设置为 1 时,当 primary 地址被删除后,第一个 secondary 地址将变成 primary 地址。如果该变量设置为 0,那么当删除 primary 地址,该接口上的所有地址都会被删除。另外,变量 net.ipv4.conf.default.promote_secondaries 的值也会影响其行为。

1
2
3
4
$ sysctl net.ipv4.conf.default.promote_secondaries
net.ipv4.conf.default.promote_secondaries = 0
$ sysctl net.ipv4.conf.lo.promote_secondaries
net.ipv4.conf.lo.promote_secondaries = 0

而对于 IPv6 地址,如果 primary 地址被删除了,那么第一个 secondary 地址将总是提升为 primary 地址,而无需关心 systemctl 设置。

邻接表(ARP、ND)管理

显示邻接表

  • 显示这个邻接表
1
ip neighbor show

这里 show 命令支持 -4 和 -6 选项,以只查看 IPv4(ARP)和 IPv6(ND)邻居。默认所有的邻居都会显示。

  • 只显示单个接口上的邻居
1
ip neighbor show dev ${interface name}

示例:

1
2
3
$ ip neighbor show dev ens32
192.168.204.2 lladdr 00:50:56:f4:ca:d8 REACHABLE
192.168.204.1 lladdr 00:50:56:c0:00:08 REACHABLE

清空接口上的邻接表

使用如下命令清空指定接口上的邻接表

1
ip neighbor flush dev ${interface name}

示例:

1
sudo ip neighbor flush dev lo

添加邻接表项

使用如下命令在一个接口上添加一条邻接表项:

1
ip neighbor add ${network address} lladdr ${link layer address} dev ${interface name}

示例:

1
2
3
4
5
6
$ sudo ip neighbor add 192.168.204.3 lladdr 10:10:10:10:10:10 dev ens32
$ ip neighbor show dev ens32
192.168.204.2 lladdr 00:50:56:f4:ca:d8 STALE
192.168.204.254 lladdr 00:50:56:fe:b9:9e STALE
192.168.204.3 lladdr 10:10:10:10:10:10 PERMANENT
192.168.204.1 lladdr 00:50:56:c0:00:08 REACHABLE

该命令的用途之一:可以将接口的 ARP 功能禁用,同时为接口添加静态静态表项,从而限制接口只能和特定机器通信。

删除邻接表项

使用如下命令在一个接口上删除一条邻接表项:

1
ip neighbor delete ${network address} lladdr ${link layer address} dev ${interface name}

示例:

1
2
3
4
5
6
$ sudo ip neighbor delete 192.168.204.3 lladdr 10:10:10:10:10:10 dev ens32
$ ip neighbor show dev ens32
192.168.204.2 lladdr 00:50:56:f4:ca:d8 STALE
192.168.204.254 lladdr 00:50:56:fe:b9:9e STALE
192.168.204.3 FAILED
192.168.204.1 lladdr 00:50:56:c0:00:08 REACHABLE

通过该命令可以删除某条静态表项,或者某条动态学习到的表项。

链路管理

链路其实就是指网络接口,ip link 系列命令可以对所有接口类型进行操作,例如查看链路信息、修改 MTU 等。从 3.16 版本以后,ip link 命令可以创建除 L2TPv3 以外的所有类型的接口。

显示接口

  • 显示所有接口的信息
1
2
ip link show
ip link list

这两个命令完全等效,可以使用相同的参数。

  • 显示指定接口的信息
1
ip link show dev ${interface name}

示例:

1
2
3
$ ip link show lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

设置接口 up/down

通过如下命令,将接口状态设置为 up 或者 down:

1
2
ip link set dev ${interface name} up
ip link set dev ${interface name} down

示例:

1
2
3
4
5
6
7
$ ip link show lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
$ sudo ip link set dev lo down
$ ip link show lo
1: lo: <LOOPBACK> mtu 65536 qdisc noqueue state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

虚拟链路在创建完成之后其初始状态为 down,如果需要使用他们需要使用该命令将接口设置为 up。

设置接口描述信息

通过如下命令为接口添加可读的描述信息:

1
ip link set dev ${interface name} alias "${description}"

示例:

1
2
3
4
5
$ sudo ip link set dev lo alias loopback_if
$ ip link show lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
alias loopback_if

通过 ip link show 可以查看接口的描述信息。

重命名接口

通过如下命令可以重命名接口:

1
ip link set dev ${old interface name} name ${new interface name}

示例:

1
2
3
4
5
6
7
8
$ sudo ip link set dev lo name loif
RTNETLINK answers: Device or resource busy
$ sudo ip link set dev lo down
$ sudo ip link set dev lo name loif
$ ip link show loif
1: loif: <LOOPBACK> mtu 65536 qdisc noqueue state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
alias loopback_if

需要注意,你不能对 up 的接口进行重命名,因此在重命名之前需要接口设置为 down。

修改链路层地址

通过如下命令修改接口的链路层地址,通常也就是 Mac 地址(对于以太设备):

1
ip link set dev ${interface name} address ${address}

示例:

1
2
3
4
5
6
7
8
9
$ ip link show lo
1: lo: <LOOPBACK> mtu 65536 qdisc noqueue state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
alias loopback_if
$ sudo ip link set dev lo address 00:00:00:00:00:01
$ ip link show lo
1: lo: <LOOPBACK> mtu 65536 qdisc noqueue state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:01 brd 00:00:00:00:00:00
alias loopback_if

修改 MTU

通过如下命令修改接口的 MTU:

1
ip link set dev ${interface name} mtu ${MTU value}

示例:

1
2
3
4
5
6
7
8
9
$ ip link show lo
1: lo: <LOOPBACK> mtu 65536 qdisc noqueue state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:01 brd 00:00:00:00:00:00
alias loopback_if
$ sudo ip link set dev lo mtu 1500
$ ip link show lo
1: lo: <LOOPBACK> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:01 brd 00:00:00:00:00:00
alias loopback_if

MTU 即最大传输单元,即接口一次能够传输的最大帧大小。如果你的以太设备支持 jumbo 帧,那么你可以通过该命令设置以太接口的 MTU,从而提高 gigabit 以太接口的性能。

使能/去使能组播

通过如下命令,可以在接口上使能或去使能组播特性:

1
2
ip link set ${interface name} multicast on
ip link set ${interface name} multicast off

除非你明确知道你在做什么,否则最好不要修改该标志。

使能/去使能 ARP

通过如下命令,可以在接口上使能/去使能 ARP 特性:

1
2
ip link set ${interface name} arp on
ip link set ${interface name} arp off

通过去使能 ARP,可以强化安全策略,限制该接口只能和特定 MAC 通信,当然此时需要在邻接表手动添加 IP/MAC 白名单,否则该接口无法和任何主机通信。但是大多数情况下都是在接入层交换机设置 MAC 安全策略,除非你明确知道你在做什么,否则最好不要修改该标志。

删除接口

通过如下命令删除指定接口:

1
ip link delete dev ${interface name}

显然,只能删除虚拟接口,例如 VLAN 接口、Bridge 接口等。

示例:

1
$ sudo ip link delete dev ens32.10

创建 VLAN 接口

使用如下命令创建 VLAN 接口:

1
ip link add name ${VLAN interface name} link ${parent interface name} type vlan id ${tag}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ sudo ip link add name ens32.10 link ens32 type vlan id 10
$ ip link show
1: lo: <LOOPBACK> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:01 brd 00:00:00:00:00:00
alias loopback_if
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:40:81:a0 brd ff:ff:ff:ff:ff:ff
3: ens32.10@ens32: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 00:0c:29:40:81:a0 brd ff:ff:ff:ff:ff:ff
$ sudo ip link set dev ens32.10 up
$ ip link show
1: lo: <LOOPBACK> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:01 brd 00:00:00:00:00:00
alias loopback_if
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:40:81:a0 brd ff:ff:ff:ff:ff:ff
3: ens32.10@ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:40:81:a0 brd ff:ff:ff:ff:ff:ff

Linux 系统上唯一支持的 VLAN 类型就是 IEEE 802.1q VLAN。一旦创建一个 VLAN 接口,那么所有从 ${parent interface} 上接收的带 VLAN tag 的、且 tag 值为 id 命令中指定的 ${tag} 的报文都会被该 VLAN 接口处理。

eth0.110 这种命名格式是惯例命名,但不是必须的,你也可以参照其他类型接口的命名方式。

创建 QinQ 接口

使用如下命令创建带双层 tag 的 QinQ 接口:

1
ip link add name ${service interface} link ${physical interface} type vlan proto 802.1ad id ${service tag}
1
ip link add name ${client interface} link ${service interface} type vlan proto 802.1q id ${client tag}

示例:

1
2
3
4
5
6
7
$ sudo ip link add name ens32.10 link ens32 type vlan proto 802.1ad id 100
$ sudo ip link add name ens32.10.100 link ens32.10 type vlan proto 802.1q id 100
$ ip --brief link show
lo DOWN 00:00:00:00:00:01 <LOOPBACK>
ens32 UP 00:0c:29:40:81:a0 <BROADCAST,MULTICAST,UP,LOWER_UP>
ens32.10@ens32 DOWN 00:0c:29:40:81:a0 <BROADCAST,MULTICAST>
ens32.10.100@ens32.10 DOWN 00:0c:29:40:81:a0 <BROADCAST,MULTICAST,M-DOWN>

802.1ad QinQ 也被称为 VLAN statcking,可以通过另一个 VLAN 传输本身就携带 VLAN tag 的报文。通常的使用场景是:在运营商网络中,有时客户想要通过运营商的网络设施来连接它们自己的网络分段,由于客户网络本身就可以包含多个 VLAN,那么当客户流量(本身携带 VLAN tag)进入运营商网络时就需要通过 QinQ 来在客户网络流量上添加第二个 tag,然后当流量离开运营商网络时,由运营商边缘设备负责将外层 vlan tag 移除。外层 tag 用于客户流量在运营商网络中的传输,同时隔离不同客户的流量。

${service tag} 就是 ISP 提供的 VLAN tag,用于在 ISP 网络中传输客户流量。而 ${client tag} 则是客户流量本身携带的 tag。

需要注意,${client interface} 的 MTU 并不会自动调整,需要你自己考虑相关的 MTU 问题。你可以将 ${client interface} 的 MTU 减少 4 字节,或者增加其 parent interface 的 MTU。

从 Linux 3.10 开始兼容标准 QinQ。

创建伪以太(macvlan)接口

使用如下命令可以创建 macvlan 接口:

1
ip link add name ${macvlan interface name} link ${parent interface} type macvlan

示例:

1
2
3
4
5
6
7
8
9
$ sudo ip link add pens32 link ens32 type macvlan
$ ip link show
1: lo: <LOOPBACK> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:01 brd 00:00:00:00:00:00
alias loopback_if
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:40:81:a0 brd ff:ff:ff:ff:ff:ff
6: pens32@ens32: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1
link/ether fa:8d:42:88:4b:8a brd ff:ff:ff:ff:ff:ff

可以将 macvlan 接口当成 parent interface 下的一个虚拟接口,但是其拥有自己的 MAC 地址以及 IP 地址。从用户角度来看,macvlan 接口和普通的以太接口没有什么区别,所有从 parent interface 上接收到的流量,如果其 MAC 地址为该 macvlan 接口的 MAC 地址,那么流量将交由该 macvlan 接口处理。

macvlan 接口也可以用于 IP 地址隔离,而不用在同一个物理接口上添加多个 IP 地址。

创建 dummy 接口

使用如下命令可以创建 dummy 接口:

1
ip link add name ${dummy interface name} type dummy

示例:

1
2
3
4
5
6
7
8
9
$ sudo ip link add name dummy0 type dummy
$ ip link show
1: lo: <LOOPBACK> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1
link/loopback 00:00:00:00:00:01 brd 00:00:00:00:00:00
alias loopback_if
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:40:81:a0 brd ff:ff:ff:ff:ff:ff
8: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 4e:13:b6:d5:f6:72 brd ff:ff:ff:ff:ff:ff

dummy 接口的工作方式和 loopback 接口类似,但是你可以创建任意多的 dummy 接口。dummy 接口主要有两个用途:

  • 用于主机内的程序通信
  • 由于 dummy 接口总是 up(除非显式将管理状态设置为 down),在拥有多个物理接口的网络上,可以将 service 地址设置为 loopback 接口或 dummy 接口的地址,这样 service 地址不会因为物理接口的状态而受影响

创建 bridge 接口

使用如下命令创建 bridge:

1
ip link add name ${bridge name} type bridge

实例:

1
2
3
4
5
6
$ ip link add name br0 type bridge
$ sudo ip link set dev br0 up
$ ip link show br0
9: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/ether 22:b5:e1:3c:34:e8 brd ff:ff:ff:ff:ff:ff
bridge 接口就是 Linux 系统提供的虚拟以太交换机,可以用于在以太接口之间透明地桥接流量,典型应用场景:在运行多个虚拟机的 hypervisor 中作为以太交换机使用。

可以为 bridge 接口添加一个 IP 地址,该 IP 地址对于所有桥接接口都是可见的。

如果该命令失败,需要检查 bridge 模块是否加载。

添加接口到 bridge

使用如下命令在 bridge 中添加一个接口:

1
ip link set dev ${interface name} master ${bridge name}

示例:

1
$ sudo ip link set dev ens32 master br0

在 bridge 中添加的接口将成为虚拟交换接口,它工作在数据链路层,并终止所有网络层操作。

从 bridge 中移除接口

使用如下命令从 bridge 中移除一个接口:

1
ip link set dev ${interface name} nomaster

示例:

1
$ sudo ip link set dev ens32 nomaster

创建 bonding 接口

使用如下命令创建 bonding 接口:

1
ip link add name ${name} type bond

示例:

1
2
3
4
$ sudo ip link add name bond0 type bond
$ ip link show bond0
11: bond0: <BROADCAST,MULTICAST,MASTER> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 1a:22:22:05:ea:c7 brd ff:ff:ff:ff:ff:ff

在实际应用中,要配置 bonding 接口(链路聚合),这条命令还不够,你还需要根据你的网络设置相应的聚合参数等。向聚合组中添加接口的命令和向 bridge 中添加接口的命令一样,需要注意,只有在接口状态为 down 时才能添加成功。

创建 ifb 接口

使用如下命令创建 ifb 接口:

1
ip link add ${interface name} type ifb

示例:

1
2
3
4
$ sudo ip link add ifb0 type ifb
$ ip link show ifb0
14: ifb0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 32
link/ether 76:38:b6:4d:93:66 brd ff:ff:ff:ff:ff:ff

ifb (Intermediate Functional Block )接口用于重定向流量以及流量 mirror,以配合 tc 使用。

创建 veth pair

使用如下命令创建 veth pair:

1
ip link add name ${first device name} type veth peer name ${second device name}

示例:

1
2
3
4
5
6
7
$ sudo ip link add name veth0 type veth peer name veth1
$ ip link show
......
15: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 36:d7:ac:11:27:9a brd ff:ff:ff:ff:ff:ff
16: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether f6:ab:0c:87:cb:57 brd ff:ff:ff:ff:ff:ff

虚拟以太设备(Virtual ethernet)总是成对出现,因此有时也被称为 veth pair。其工作方式就像双向管道,无论流量从哪一端进入,都将从另一端发出。典型的应用场景是:在不同网络 namespace 之间通过 veth 进行连接。

接口组管理

接口组类似于交换机中的端口范围,你可以将一个网络接口添加到一个指定的接口组中,然后对该接口组执行操作,就等效于对接口组中的每个接口执行这些操作。没有添加到任何接口组中的接口,默认属于 group 0(也称为 default group)。

  • 添加接口到 group

通过如下命令将接口添加到指定 group 中:

1
ip link set dev ${interface name} group ${group number}

示例:

1
2
$ sudo ip link set dev ens32 group 1
$ sudo ip link set dev ens35 group 1
  • 将接口从 group 中移除

通过将接口添加到 group 0 中,可以将接口从其他 group 中移除

1
2
ip link set dev ${interface name} group 0
ip link set dev ${interface} group default

示例:

1
2
$ sudo ip link set dev ens32 group 0
$ sudo ip link set dev ens35 group 0
  • 对 group 设置符号名

group 的符号名保存在 /etc/iproute2/group 文件中,group 0 的 符号名 default 就是来自这里:

1
2
3
$ cat /etc/iproute2/group
# device group names
0 default

一旦你为 group 设置符号名之后,在 ip 命令中可以随意使用 group 号或 group 名。可以如下方式为 group 设置符号名:

1
2
3
4
5
$ sudo sh -c 'echo "10    customer-vlans" >> /etc/iproute2/group'
$ cat /etc/iproute2/group
# device group names
0 default
10 customer-vlans

之后你可以在所有操作中使用该名称,例如:

1
$ sudo ip link set dev lo group customer-vlans
  • 对 group 执行操作

使用如下命令,对 group 中的接口执行批量操作:

1
ip link set group ${group number} ${operation and arguments}

示例:

1
2
3
4
$ sudo ip link set group 10 mtu 1000
$ ip link show lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 1000 qdisc noqueue state UNKNOWN mode DEFAULT group customer-vlans qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  • 查看指定 group 中接口的信息

在普通的显示命令中,结合 group ${group} 可以查看 group 中所有接口的信息:

1
2
3
4
5
6
7
8
$ ip link list group 10
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 1000 qdisc noqueue state UNKNOWN mode DEFAULT group customer-vlans qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
$ ip address show group customer-vlans
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 1000 qdisc noqueue state UNKNOWN group customer-vlans qlen 1
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever

Tun 和 Tap 设备

Tun 和 Tap 设备允许用户空间程序仿真一个网络设备。当用户空间程序打开 Tup 或 Tap 设备时,它们将得到一个文件描述符。内核网络栈路由到这些设备的报文都可以通过该文件描述符进行读取,用户态程序通过该文件描述符写入的数据如同本地向外发出的报文一样,也将注入到内核网络栈进行处理。

这两类设备的差异如下:

  • tap 设备发送和接收原始以太帧
  • tun 设备爱发送和接收原始 IP 数据报

有两种类型的 tun/tap 设备:persistent(永久性的)和 transient(瞬时性的)。当用户态程序打开某个特定设备时,永久性的 tun/tap 设备就被创建,当相关的文件描述符关闭时,对应的 tun/tap 设备也自动销毁。

以下列出的命令用来操纵 persistent 设备。

查看 tun/tap 设备

通过如下命令查看 tun/tap 设备:

1
2
ip tuntap
ip tuntap show

通过该命令能够确定设备是处于 tun 还是 tap 模式。

添加 tun/tap 设备(root 用户使用)

通过如下命令创建的 tun/tap 设备,只能被 root 用户使用:

1
ip tuntap add dev ${interface name} mode ${mode}

示例:

1
2
3
4
5
6
7
$ sudo ip tuntap add dev tun0 mode tun
$ ip tuntap show
tun0: tun UNKNOWN_FLAGS:800
$ sudo ip tuntap add dev tap0 mode tap
$ ip tuntap show
tap0: tap UNKNOWN_FLAGS:800
tun0: tun UNKNOWN_FLAGS:800

添加 tun/tap 设备(普通用户使用)

通过如下命令创建的 tun/tap 可以被普通用户使用:

1
ip tuntap add dev ${interface name} mode ${mode} user ${user} group ${group}

示例:

1
2
3
$ sudo ip tuntap add dev tun1 mode tun user p4 group p4
$ ip tuntap show
tun1: tun UNKNOWN_FLAGS:800 user 1001 group 1001

添加 tun/tap 设备,并指定报文格式

通过如下命令,在添加 tun/tap 设备的同时,可以指定通过文件描述符所接收到的报文的元信息。

1
ip tuntap add dev ${interface name} mode ${mode} pi

示例:

1
2
3
$ sudo ip tuntap add dev tap1 mode tap pi
$ ip tuntap show
tap1: tap pi UNKNOWN_FLAGS:800

添加 tun/tap 设备,并忽略流控

正常情况下,报文通过 tun/tap 设备发送时,其发送流程和其他设备的发送流程是一样。报文被添加到一个队列中,然后由流量控制引擎进行处理(通过 tc 命令进行配置)。这个过程可以被旁路(bypassed),这样就禁用了 tun/tap 设备上的流控机制。

1
ip tuntap add dev ${interface name} mode ${mode} one_queue

示例:

1
2
3
$ sudo ip tuntap add dev tun2 mode tun one_queue
$ ip tuntap show
tun2: tun one_queue UNKNOWN_FLAGS:800

删除 tun/tap 设备

使用如下命令删除 tun/tap 设备:

1
ip tuntap del dev ${interface name} mode ${mode}

示例:

1
2
3
4
5
$ sudo ip tuntap delete dev tap1 mode tap
$ sudo ip tuntap delete dev tun1 mode tun
$ sudo ip tuntap delete dev tun2 mode tun
$ sudo ip tuntap delete dev tun0 mode tun
$ ip tuntap show

使用该命令时必须指定 mode,ip link show 的输出中不会显示其 mode,需要使用命令 ip tuntap show

隧道管理

隧道看起来和普通的网络接口一样,但是通过隧道发送的报文,会被封装成另一种协议,然后被送到隧道的另一端,这期间可能要经过多个网络设备,然后在隧道的另一端进行解封装,再按照正常方式进行发送。通过隧道,你可以认为两个机器之间是直连的,但事实上却不是。

隧道通常用于虚拟局域网(Virtual Private Network,VPN),并配合加密传输协议如 IPSec 一起使用,或者用于连接两个使用相同协议的网络(但是中间传输网络却使用不同的协议),例如被 IPv4 网络分隔的两个 IPv6 网络。

需要注意,隧道本身不提供安全性,它们的安全性和 underlay 网络是一样的。所以如果需要考虑隧道安全,可以使用加密传输,例如 IPsec。

Linux 当前支持 IPIP(IPv4 in IPv4),SIT(IPv6 in IPv4),IPIP6(IPv4 in IPv6),IP6IP6(IPv6 in IPv6),GRE(virtually anything to anything)以及最近新增的 VTI(IPv4 in IPsec)。

需要注意,隧道创建之后,其初始状态为 down 状态,需要手动将其设置为 up。

在以下命令中,${local endpoint address}${remote endpoint address} 代表端点物理接口的地址,${address} 代表隧道接口的地址。

创建 IPIP 隧道

使用如下命令,创建 IPIP(IPv4 in IPv4)隧道:

1
ip tunnel add ${interface name} mode ipip local ${local endpoint address} remote ${remote endpoint address}

示例:

1
2
3
4
5
6
7
$ sudo ip tunnel add tun0 mode ipip local 192.0.2.1 remote 198.51.100.3
$ sudo ip link set dev tun0 up
$ sudo ip address add 10.0.0.1/30 dev tun0
$ ip -4 address show
11: tun0@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1
inet 10.0.0.1/30 scope global tun0
valid_lft forever preferred_lft forever

创建 SIT 隧道

使用如下命令,创建 SIT(IPv6 in IPv4) 隧道:

1
sudo ip tunnel add ${interface name} mode sit local ${local endpoint address} remote ${remote endpoint address}

示例:

1
2
3
4
5
6
7
8
$ sudo ip tunnel add tun9 mode sit local 192.0.2.1 remote 198.51.100.3
$ sudo ip link set dev tun9 up
$ sudo ip address add 2001:db8:1::1/64 dev tun9
13: tun9@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 state UNKNOWN qlen 1
inet6 2001:db8:1::1/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::c000:201/64 scope link
valid_lft forever preferred_lft forever

该类型的隧道通常用于为使用 IPv4 网络互连的两个 IPv6 网络之间提供连通性。

创建 IPIP6 隧道

使用如下命令,创建 IPIP6(IPv4 in IPv6)隧道:

1
ip -6 tunnel add ${interface name} mode ipip6 local ${local endpoint address} remote ${remote endpoint address}

示例:

1
2
3
4
5
6
7
8
9
10
$ sudo ip -6 tunnel add tun8 mode ipip6 local 2001:db8:1::1 remote 2001:db8:1::2
$ sudo ip link set dev tun8 up
$ sudo ip address add 192.0.2.1 dev tun8
$ ip -r address show tun8
15: tun8@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1432 qdisc noqueue state UNKNOWN group default qlen 1
link/tunnel6 2001:db8:1::1 peer 2001:db8:1::2
inet 192.0.2.1/32 scope global tun8
valid_lft forever preferred_lft forever
inet6 fe80::50e7:55ff:feee:5aab/64 scope link
valid_lft forever preferred_lft forever

创建 IP6IP6 隧道

使用如下命令,创建 IP6IP6(IPv6 in IPv6)隧道:

1
ip -6 tunnel add ${interface name} mode ip6ip6 local ${local endpoint address} remote ${remote endpoint address}

示例:

1
2
3
4
5
6
7
8
9
$ sudo ip -6 tunnel add tun3 mode ip6ip6 local 2001:db8:1::3 remote 2001:db8:1::4
$ sudo ip link set dev tun3 up
$ sudo ip address add 2001:db8:2:2::1/64 dev tun3
$ ip -6 address show tun3
16: tun3@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1432 state UNKNOWN qlen 1
inet6 2001:db8:2:2::1/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::68cb:22ff:fe4d:b42c/64 scope link
valid_lft forever preferred_lft forever

创建 gretap 设备

使用如下命令,创建 gretap(ethernet over GRE)设备:

1
ip link add ${interface name} type gretap local ${local endpoint address} remote ${remote endpoint address}

示例:

1
2
3
4
5
$ sudo ip link add gretap3 type gretap local 192.0.2.3 remote 203.0.113.3
$ sudo ip link set dev gretap3 up
$ sudo ip link show gretap3
19: gretap3@NONE: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1462 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 1000
link/ether 62:ac:61:24:af:1d brd ff:ff:ff:ff:ff:ff

这种类型的隧道将以太帧封装到 IPv4 报文中。最新的 Linux kernel 和 iproute2 版本也支持 gretap over IPv6,只需要将 type 设置为 ip6gretap 即可创建基于 IPv6 的链路。使用这种命令创建的隧道就如同一个二层链路,可以被加入到 bridge 中。

该类型的隧道通常用于通过一个路由网络连接两个二层网段。

创建 GRE 隧道

使用如下命令,创建 GRE 隧道:

1
ip tunnel add ${interface name} mode gre local ${local endpoint address} remote ${remote endpoint address}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ sudo ip tunnel add tun7 mode gre local 192.0.2.7 remote 192.0.2.7
$ sudo ip link set dev tun7 up
$ sudo ip address add 192.168.0.1/30 dev tun7
$ sudo ip address add 2001:db8:1::1/64 dev tun7
$
$ ip address show tun7
20: tun7@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1476 qdisc noqueue state UNKNOWN group default qlen 1
link/gre 192.0.2.7 peer 192.0.2.7
inet 192.168.0.1/30 scope global tun7
valid_lft forever preferred_lft forever
inet6 2001:db8:1::1/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::5efe:c000:207/64 scope link
valid_lft forever preferred_lft forever

GRE 隧道可以同时封装 IPv4 和 IPv6 流量,默认它使用 IPv4 进行传输,如果向使用 GRE over IPv6,可以将 mode 指定为 ip6gre。

创建到某个端点的多个 GRE 隧道

使用如下命令,创建到某个端点的多个 GRE 隧道:

1
ip tunnel add ${interface name} mode gre local ${local endpoint address} remote ${remote endpoint address} key ${key value}

示例:

1
2
$ sudo ip tunnel add tun11 mode gre local 192.0.2.11 remote 203.0.113.11 key 123
$ sudo ip tunnel add tun12 mode gre local 192.0.2.11 remote 203.0.113.11 key 124

注意,这里的 key 并不会提供任何安全特性,而是仅仅作为一个标识符来区分各个隧道。

创建点到多点 GRE 隧道

使用如下命令创建点到多点隧道:

1
ip tunnel add ${interface name} mode gre local ${local endpoint address} key ${key value}

示例:

1
2
3
4
5
$ ip tunnel add tun13 mode gre local 192.0.2.13 key 130
$ sudo ip link set dev tun13 up
$ sudo ip address add 10.0.0.1/27 dev tun13
$ ip tunnel show tun13
tun13: gre/ip remote any local 192.0.2.13 ttl inherit key 130

注意,这里没有指定 ${remote endpoint address},由于没有指定 ${remote endpoint address},key 是唯一的方式来区分 tunnel 流量。所以 ${key value} 是必须的。

该类型的隧道允许你通过隧道接口和多个远端端点通信,它通常用于复杂的 VPN 环境中,用于实现一对多的通信(在 Cisco 术语中称为 dynamic multipoint VPN)。

由于没有指定远端端点的地址,所以上述命令不足以创建出隧道,你的系统需要知道另一端点位于哪里。实际应用中 NHRP(Next Hop Resolution Protocol)用于实现该目的。如果是处于测试目的,你可以手动添加对端。

假设对端物理接口的 IP 地址为 203.0.113.6,隧道接口地址为 10.0.0.2,使用如下方式添加对端

1
ip neighbor add 10.0.0.2 lladdr 203.0.113.6 dev tun8

在对端上也要进行手动配置。有趣的是,这里链路层地址和邻接地址都是 IP 地址。

创建 GRE over IPv6 隧道

最近的内核和 iproute2 版本已经支持 GRE over IPv6,使用如下命令创建不带 key 的点到点隧道:

1
ip -6 tunnel add name ${interface name} mode ip6gre local ${local endpoint} remote ${remote endpoint}

对于上述 IPv4 GRE 隧道所支持的所有选项和特性,GRE over IPv6 也同样支持。

删除隧道

使用如下命令删除隧道:

1
2
ip tunnel del ${interface name}
ip tunnel delete ${interface name}

示例:

1
$ sudo ip tunnel delete tun0

修改隧道

使用如下命令修改隧道的某些选项参数:

1
ip tunnel change ${interface name} ${options}

示例:

1
2
3
4
5
$ ip tunnel show tun9
tun9: ipv6/ip remote 198.51.100.3 local 192.0.2.1 ttl inherit 6rd-prefix 2002::/16
$ sudo ip tunnel change tun9 remote 198.51.100.4
$ ip tunnel show tun9
tun9: ipv6/ip remote 198.51.100.4 local 192.0.2.1 ttl inherit 6rd-prefix 2002::/16

对于 unkeyed 隧道,你不能给其设置 key。你也不能通过这种方式修改隧道的模式。

查看隧道信息

使用如下命令查看隧道信息:

1
2
ip tunnel show
ip tunnel show ${interface name}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ip tunnel show
gre0: gre/ip remote any local any ttl inherit nopmtudisc
sit0: ipv6/ip remote any local any ttl 64 nopmtudisc 6rd-prefix 2002::/16
tun9: ipv6/ip remote 198.51.100.4 local 192.0.2.1 ttl inherit 6rd-prefix 2002::/16
tun13: gre/ip remote any local 192.0.2.13 ttl inherit key 130
tun12: gre/ip remote 203.0.113.11 local 192.0.2.11 ttl inherit key 124
tun11: gre/ip remote 203.0.113.11 local 192.0.2.11 ttl inherit key 123
tun7: gre/ip remote 192.0.2.7 local 192.0.2.7 ttl inherit
tunl0: ip/ip remote any local any ttl inherit nopmtudisc
$ ip -6 tunnel show
tun8: ip/ipv6 remote 2001:db8:1::2 local 2001:db8:1::1 encaplimit 4 hoplimit 64 tclass 0x00 flowlabel 0x00000 (flowinfo 0x00000000)
ip6tnl0: ipv6/ipv6 remote :: local :: encaplimit 0 hoplimit 0 tclass 0x00 flowlabel 0x00000 (flowinfo 0x00000000)
tun3: ipv6/ipv6 remote 2001:db8:1::4 local 2001:db8:1::3 encaplimit 4 hoplimit 64 tclass 0x00 flowlabel 0x00000 (flowinfo 0x00000000)

L2TPv3 伪线管理

L2TPv3 是一种隧道协议,通常用于实现 L2 伪线。

在许多发行版本中,L2TPv3 被编译成一个模块,默认没有被加载。如果在使用 ip l2tp 命令时得到一些错误信息,可能就是由于 L2TPv3 模块没有被加载。L2TPv3 涉及到 l2tp_netlink 和 l2tp_eth 两个内核模块,如果你想使用 L2TPv3 over IP 而不是 L2TPv3 over UDP,那么还需要加载 l2tp_ip

和其他 Linux 隧道协议的实现不同,在 L2TPv3 中首先需要创建一个隧道,然后将会话绑定到隧道上。你可以将具有不同标识符的多个会话绑定到同一个隧道。虚拟网络接口(默认命名为 l2tpethX)和会话相关联。

注意,Linux 内核实现只能处理 L2TPv3 的数据平面,所以使用 iproute2 只能创建 unmanaged 隧道,也就是说隧道的两端都需要手工配置。

创建 L2TPv3 over UDP 隧道

使用如下命令,创建 L2TPv3 over UDP 隧道:

1
2
ip l2tp add tunnel tunnel_id ${local tunnel numeric identifier} peer_tunnel_id ${remote tunnel numeric identifier} udp_sport ${source port} udp_dport ${destination port} encap udp
local ${local endpoint address} remote ${remote endpoint address}

示例:

1
$ sudo ip l2tp add tunnel tunnel_id 1 peer_tunnel_id 1 udp_sport 5000 udp_dport 5000 encap udp local 192.0.2.1 remote 203.0.113.2

隧道两端的标识符和其他设置必须相匹配。

创建 L2TPv3 over IP 隧道

使用如下命令,创建 L2TPv3 over IP 隧道:

1
ip l2tp add tunnel tunnel_id ${local tunnel numeric identifier} peer_tunnel_id {remote tunnel numeric identifier } encap ip local ${local endpoint address} remote ${remote endpoint address}

示例:

1
$ sudo ip l2tp add tunnel tunnel_id 2 peer_tunnel_id 2 encap ip local 192.0.2.1 remote 203.0.113.2

基于 IP 的 L2TPv3 封装格式的封装开销更小,但是一般无法穿越 NAT。

创建 L2TPv3 会话

使用如下命令创建 L2TPv3 会话:

1
ip l2tp add session tunnel_id ${local tunnel identifier} session_id ${local session numeric identifier} peer_session_id ${remote session numeric identifier}

示例:

1
$ sudo ip l2tp add session tunnel_id 1 session_id 10 peer_session_id 10

隧道 id 必须匹配之间所创建隧道的 id 值,会话两端的标识符也必须匹配。一旦你创建了隧道和会话,l2tpethX 接口就会自动出现,但是初始状态为 down,需要手动将其状态设置为 up。

删除 L2TPv3 会话

使用如下命令删除 L2TPv3 会话:

1
ip l2tp del session tunnel_id ${tunnel identifier} session_id ${session identifier}

示例:

1
$ sudo ip l2tp del session tunnel_id 1 session_id 10

删除 L2TPv3 隧道

使用如下命令删除 L2TPv3 隧道:

1
ip l2tp del tunnel tunnel_id ${tunnel identifier}

示例:

1
$ sudo ip l2tp del tunnel tunnel_id 1

在删除隧道之前,需要删除和该隧道相关联的所有会话。

查看 L2TPv3 隧道信息

使用如下命令查看 L2TPv3 隧道信息:

1
2
ip l2tp show tunnel
ip l2tp show tunnel tunnel_id ${tunnel identifier}

示例:

1
2
3
4
5
6
7
8
$ sudo ip l2tp show tunnel
Tunnel 2, encap IP
From 192.0.2.1 to 203.0.113.2
Peer tunnel 2
Tunnel 1, encap UDP
From 192.0.2.1 to 203.0.113.2
Peer tunnel 1
UDP source / dest ports: 5000/5000

查看 L2TPv3 会话信息

使用如下命令查看 L2TPv3 会话信息:

1
2
ip l2tp show session
ip l2tp show session session_id ${session identifier} tunnel_id ${tunnel identifier}

示例:

1
2
3
4
5
6
7
8
9
10
$ sudo ip l2tp show session
Session 10 in tunnel 1
Peer session 10, tunnel 1
interface name: l2tpeth0
offset 0, peer offset 0
$ sudo ip l2tp show session session_id 10 tunnel_id 1
Session 10 in tunnel 1
Peer session 10, tunnel 1
interface name: l2tpeth0
offset 0, peer offset 0

VXLAN 管理

VXLAN 是一个二层隧道协议,通常用于连接运行在不同 hypervisor 节点上的虚拟机。和 GRE 或 L2TPv3 这种点对点隧道不同,VXLAN 可以通过 IP 多播提供多点访问特性。通过在数据帧中携带网络标识符,VXLAN 也支持虚拟网络之间的隔离。VXLAN 的底层封装协议是 UDP。

创建单播 VXLAN 链路

使用如下命令创建单播 VXLAN 链路:

1
ip link add name ${interface name} type vxlan id <0-16777215> dev ${source interface} remote ${remote endpoint address} local ${local endpoint address} dstport ${VXLAN destination port}

示例:

1
$ sudo ip link add name vxlan0 type vxlan id 42 dev eth0 remote 203.0.113.6 local 192.0.2.1 dstport 4789

这里的 id 选项就是 VXLAN 中的 VNI(VXLAN Network Identifier)。

创建多播 VXLAN 链路

使用如下命令创建多播 VXLAN 链路:

1
ip link add name ${interface name} type vxlan id <0-16777215> dev ${source interface} group ${multicast address} dstport ${VXLAN destination port}

示例:

1
$ sudo ip link add name vxlan1 type vxlan id 42 dev eth0 group 239.0.0.1 dstport 4789

路由管理

以下是在 Linux 系统进行路由管理的一些注意事项:

  • 对于 IPv4 路由,你可以使用点分十进制或前缀长度来指定子网掩码。也就是说,192.0.2.0/24 和 192.0.2.0/255.255.255.0 两种方式是等效的
  • 如果你创建了一条静态路由,但是如果因为接口 down 而导致该路由变得不可达,该路由会被移除,并且不会自动恢复
  • 如果你想将 Linux 主机作为一台路由器,需要考虑安装路由协议套件,例如 Quagga 或 BIRD。它们可以跟踪接口的状态,并且当接口由 down 状态恢复成 up 状态时,自动恢复路由。当然这些套件也允许你使用动态路由协议,例如 OSPF 和 BGP
  • 直连路由:当你为某个网络接口设置 IP 地址时,系统会自动计算它的网络地址,并生成到该网络的一条路由。这种类型的路由也被称为直连路由。当接口变为 down 时,和该接口相关的直连路由也会被移除

查看所有路由

使用如下命令查看所有路由:

1
2
ip route
ip route show

示例:

1
2
3
4
5
6
7
8
9
$ ip -4 route
default via 192.168.204.2 dev ens32 proto static metric 100
169.254.0.0/16 dev ens32 scope link metric 1000
172.16.27.0/24 dev ens35 proto kernel scope link src 172.16.27.128 metric 100
192.168.204.0/24 dev ens32 proto kernel scope link src 192.168.204.145 metric 100
$ ip -6 route
fe80::/64 dev ens35 proto kernel metric 256 pref medium
fe80::/64 dev gretap3 proto kernel metric 256 mtu 1462 pref medium
fe80::/64 dev ens32 proto kernel metric 256 pref medium

你可以使用 -4 或 -6 选项来只查看 IPv4 路由或 IPv6 路由。如果没有指定选项,默认将只显示 IPv4 路由。

查看到指定网络及其所有子网的路由

使用如下命令查看到指定网络及其所有子网的所有路由:

1
ip route show to root ${address}/${mask}

示例:

1
2
$ ip route show to root 192.168.0.0/16
192.168.204.0/24 dev ens32 proto kernel scope link src 192.168.204.145 metric 100

这里命令字 to 是可选的(后文命令中的 to 也都是可选的)。

查看到指定网络及其所有超网的路由

使用如下命令查看到指定网络及其所有超网的所有路由:

1
ip route show to match ${address}/${mask}

示例:

1
2
3
$ ip route show to match 192.168.204.0/24
default via 192.168.204.2 dev ens32 proto static metric 100
192.168.204.0/24 dev ens32 proto kernel scope link src 192.168.204.145 metric 100

路由器在进行三层转发时执行的是最长前缀匹配,当到特定子网的流量被错误路由时,可以使用这些命令进行调试。

查看到特定子网的路由

使用如下命令查看到特定子网的精确路由:

1
ip route show to exact ${address}/${mask}

示例:

1
2
$ ip route show to exact 192.168.204.0/24
192.168.204.0/24 dev ens32 proto kernel scope link src 192.168.204.145 metric 100

查看内核所使用的路由

使用如下命令查看到指定网络内核所使用的路由:

1
ip route get ${address}/${mask}

示例:

1
2
3
$ ip route get 192.168.204.0/24
broadcast 192.168.204.0 dev ens32 src 192.168.204.145
cache <local,brd>

在复杂路由场景中(例如多路径路由),该结果可能正确但是不完全,因为它总是显示首先使用的第一条路由。绝大多数情况下这不会有什么问题。

查看路由缓存区

使用如下命令查看路由缓存区:

1
ip route show cached

在内核版本 3.6 之前,Linux 使用路由缓存区,该命令显示路由缓存区的内容,上面命令的修饰关键字同样适用于该命令。在更新的内核上,该命令无效果。

以指定 gateway 的方式增加路由

使用如下命令,以指定 gateway 的方式增加一条路由表项:

1
ip route add ${address}/${mask} via ${next hop}

示例:

1
sudo ip route add 192.168.203.0/24 via 192.168.204.1

以指定接口的方式增加路由

使用如下命令,以指定接口的方式增加一条路由表项:

1
ip route add ${address}/${mask} dev ${interface name}

示例:

1
sudo ip route add 192.168.202.0/24 dev ens32

以指定接口的方式指定路由通常适用于点对点链路(例如 PPP 隧道),此时并不需要指定下一跳地址。

改变或替换路由

可以使用 change 命令修改已存在路由的一些参数,而 replace 命令也可以已存在路由的参数,但是如果路由不存在,replace 命令将新增该路由。

示例:

1
2
ip route change 192.168.2.0/24 via 10.0.0.1
ip route replace 192.0.2.1/27 dev tun0

删除路由

使用如下命令删除一条路由:

1
ip route delete ${rest of the route statement}

示例:

1
$ sudo ip route delete 192.168.204.0/24 dev ens32

添加默认路由

使用如下快捷命令添加默认路由:

1
2
ip route add default via ${address}/${mask}
ip route add default dev ${interface name}

该命令等效于:

1
2
ip route add 0.0.0.0/0 via ${address}/${mask}
ip route add 0.0.0.0/0 dev ${interface name}

对于 IPv6,该命令也同样适用,其等效于添加了 ::/0 这条路由表项:

示例:

1
$ sudo ip -6 route add default via 2001:db8::1

添加黑洞路由

使用如下命令添加黑洞路由:

1
ip route add blackhole ${address}/${mask}

示例:

1
$ sudo ip route add blackhole 192.0.2.1/32

匹配黑洞路由的流量将直接被丢弃。使用黑洞路由有两个目的:

  • 对于目的地为某些特定地址的流量,直接将其丢弃
  • 有时需要通过创建黑洞路由,来控制路由的通告

创建特殊路由

使用如下命令创建特殊类型的路由:

1
2
3
ip route add unreachable ${address}/${mask}
ip route add prohibit ${address}/${mask}
ip route add throw ${address}/${mask}

匹配这些路由表项的流量,会被系统丢弃,同时向发送者返回 ICMP 错误消息。

  • unreachable:返回 ICMP 主机不可达错误
  • prohibit:返回 ICMP administratively prohibited 错误
  • throw:返回 ICMP 网络不可达错误

不像黑洞路由,并不推荐使用这些特殊路由来丢弃某些流量(例如 DDoS),因为这些路由会对每个丢弃的报文都生成一个 ICMP 应答报文,这样就消耗了额外系统资源和网络资源。这些路由可以用来实现网络访问策略。另外这些路由类型可以用于实现基于策略的路由,在非默认路由表中,这些路由将停止当前路由表的查找,并且不返回 ICMP 错误消息。

设置路由的度量

使用如下命令为路由设置设置度量:

1
ip route add ${address}/${mask} via ${gateway} metric ${number}

示例:

1
$ sudo ip route add 192.168.201.0/24 dev ens32 metric 5

如果到达某个网络有多条路由,但是这些路由的度量值不同,具有最低度量值的路由将优先考虑。通过该特性,可以为某些重要目的网络提供备份路由。

多路径路由

使用如下命令,为目的网络添加多路径路由:

1
ip route add ${addresss}/${mask} nexthop via ${gateway 1} weight ${number} nexthop via ${gateway 2} weight ${number}

示例:

1
2
3
4
5
$ sudo ip route add 192.168.200.0/24 nexthop via 192.168.204.1 weight 1 nexthop dev ens32 weight 10
$ ip route show exact 192.168.200.0/24
192.168.200.0/24
nexthop via 192.168.204.1 dev ens32 weight 1
nexthop dev ens32 weight 10

通过多路径路由,系统可以根据路径的权重来实现非等价负载均衡,在这个命令中,可以混合使用 gateway 和 interface 来指定下一跳。

策略路由

在 Linux 系统中,基于策略的路由(Policy-based routing,PBR)按照如下方式进行设计:

  • 创建自定义路由表
  • 创建 rules 来告诉内核对特定的流量应该使用自定义路由表,而非默认路由表

已经预先定义了一些路由表:

  • local(table 255):包括控制本地和广播地址的路由
  • main(table 254):包含所有的非 PBR路由,当你添加路由时如果没有指定路由表,添加的路由将存放在这里
  • default(table 253):用于预处理,正常情况下不使用

对于用户自定义的路由表,当向该路由表中添加第一条路由时,该路由表就自动被创建。

创建策略路由

使用如下命令创建一条策略路由:

1
ip route add ${route options} table ${table id or name}

示例:

1
$ sudo ip route add 192.168.204.0/24 via 192.168.204.1 table 10

前面路由管理一节所使用的各种选项也同样适用于策略路由,唯一的区别是策略路由的命令中会包含 table ${table id or name} 部分。

table 的数字标识符和字符标识符可以交替使用。如果想为 table 指定符号名,也可以编辑文件 /etc/iproute2/rt_tables。

delete、change、replace 或其他路由动作都可以适用于策略路由表。而且,如果操作的 table 为 main,那么其实 table 部分可以不用指定。

查看策略路由

使用如下命令查看策略路由

1
ip route show table ${table id or name}

示例:

1
2
$ ip route show table 10
192.168.204.0/24 via 192.168.204.1 dev ens32

通用 rule 语法

使用如下命令创建一条 rule:

1
ip rule add ${options} <lookup ${table id or name}|blackhole|prohibit|unreachable>

如果使用了 lookup 命令关键字,那么匹配 ${options} 的流量将根据 ${table id or name} 所指定的路由表进行路由查找,而不是默认的 main/254 路由表。而 backhole、prohibit 以及 unreachable 的工作方式和路由管理一节中相应的路由类型工作方式一致。

对于 IPv6 rules,可以使用 -6 选型。另外,如果动作是 lookup,可以省略关键字 lookup。

创建匹配源网络的 rule

使用如下命令,创建一条匹配源网络的 rule

1
ip rule add from ${source network} ${action}

示例:

1
$ sudo ip rule add from 192.168.203.0/24 lookup 10

创建匹配目的网络的 rule

使用如下命令,创建一条匹配目的网络的 rule

1
ip rule add to ${destination network} ${action}

示例:

1
$ sudo ip rule add to 192.168.200.0/24 blackhole

创建匹配 ToS 字段的 rule

使用如下命令,创建一条匹配 tos 的 rule

1
ip rule add tos ${ToS value} ${action}

示例:

1
$ sudo ip rule add tos 10 lookup 10

创建匹配防火墙标志的 rule

使用如下命令,创建匹配防火墙标志的 rule

1
ip rule add fwmark ${mark} ${action}

示例:

1
$ sudo ip rule add fwmark 10 lookup 10

对于防火墙标志规则,需要将其插入到路由决策前的处理链中,否则它不会有效果。关于 netfilter 的处理流程可以参考 netfilter flowchart。

创建匹配入接口的 rule

使用如下命令创建匹配入接口的 rule:

1
ip rule add iif ${interface name} ${action}

示例:

1
$ sudo ip rule add iif ens32 lookup 10

创建匹配出接口的 rule

使用如下命令创建匹配出接口的 rule

1
ip rule add oif ${interface name} ${action}

示例:

1
$ sudo ip rule add oif ens32 lookup 10

创建匹配用户 id 的 rule

使用如下命令创建一条匹配用户 id 范围的 rule:

1
ip rule add uidrange ${start}-${end}

如果该规则想匹配单个用户,只需要将用户 id 范围的 start 和 end 值设置为同一个值即可。

设置 rule 优先级

使用如下命令设置 rule 的优先级:

1
ip rule add ${options} ${action} priority ${value}

示例:

1
2
3
4
$ sudo ip rule add oif lo lookup 10 priority 100
$ ip rule show
0: from all lookup local
100: from all oif lo lookup 10

由于规则是按低优先级到高优先级依次处理,所以需要将更具体的规则放在前面(优先级更低)。

显示所有 rule

使用如下命令显示所有 rule:

1
2
ip rule show
ip -6 rule show

删除一条 rule

使用如下命令删除指定 rule:

1
ip rule del ${options} ${action}

示例:

1
$ sudo ip rule delete oif lo lookup 10

删除所有 rule

使用如下命令删除所有 rule:

1
2
ip rule flush
ip -6 rule flush

需要注意,该命令破坏性非常大,即使你没有配置任何 rule,默认也会创建 from all lookup main rule。所以即使是未显式配置 rule 的机器,你也可以看到如下 rule:

1
2
3
4
$ ip rule show
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
1
2
3
$ ip -6 rule show
0: from all lookup local
32766: from all lookup main

from all lookup local rule 是一条特殊规则,不能被删除。但是 from all lookup main 并不是,有时你可能想要删除这条规则,例如你只想使用你显式创建的 rule 来路由流量。因此在执行 ip rule flush 命令之后,这条规则会被删除,这可能导致你的系统无法路由任何流量。

VRF 管理

VRF(Virtual Routing and Forwarding)是一种用来将某个网络的路由隔离到单独路由表的机制。它允许在同一个路由器上建立多个网络,每个网络的 IP 编址都相互独立,互不影响。典型的应用场景是建立多租户环境和 VPN,在每个 VPN 中客户都可以独立编址。

它和基于策略的路由的主要区别是:在 PBR 中使用的其他非默认路由表只能隔离静态路由,不能隔离直连路由,所以它们不能解决地址冲突。而当一个网络接口绑定到某个 VRF,该网络接口上的所有直连路由将会存放到某个单独的路由表中。

也不像 network namespace,VRF 完全工作在网络层。VRF 并不会创建完全隔离的网络栈,也无法和二层协议交互,例如 LLDP,一个进程可以将一个 socket 绑定到多个 VRF(对于路由协议进程或 IPsec 进程非常有用)。

创建 VRF

使用如下命令创建一个 VRF:

1
ip link add ${name} type vrf table ${table}

示例:

1
$ sudo ip link add foo type vrf table 100

如果该路由表不存在,将自动创建该路由表。

显示 VRF

使用如下命令显示 VRF:

1
2
ip vrf show
ip link show vrf ${vrf}

该命令的输出结果只显示 VRF 和路由表之间的绑定关系,并不会显示 VRF 和 网络接口之间的关联。如果想查看某个 VRF 中的所有接口,可以使用命令 ip link show vrf ${vrf}。

绑定接口到 VRF

使用如下命令将接口绑定到某个 VRF:

1
ip link set ${interface} master ${vrf}

和该接口相关的所有直连路由都会被移动到 VRF 的路由表中。

将接口从 VRF 中移除

使用如下命令将接口从 VRF 中移除:

1
ip link set ${interface} nomaster

和接口相关的所有直连路由将重新移动回默认路由表(main table)。

在 VRF 中运行一个命令

使用如下命令在 VRF 中运行某个命令:

1
ip vrf exec ${vrf} ${command}

由于路由器将根据 VRF 中的路由表来转发流量,因此该命令有助于调试。

网络 namespace 管理

网络 namespace 用来在一个机器内隔离多个网络栈实例。它们可以用于实现安全域隔离、虚拟机之间的流量管理,等等。每一个 namespace 都是一个完全独立的网络栈,拥有自己的接口、地址、路由等等。

创建 namespace

使用如下命令创建 namespace :

1
ip netns add ${namespace name}

示例:

1
$ sudo ip netns add host1

列出已经存在的 namespace

使用如下命令列出 namespace:

1
ip netns list

删除 namespace

使用如下命令删除 namespace:

1
ip netns delete ${namespace name}

示例:

1
$ sudo ip netns delete host1

在 namespace 中运行命令

使用如下命令在 namespace 中运行一个命令:

1
ip netns exec ${namespace name} ${command}

示例:

1
2
3
4
5
6
7
8
9
10
11
$ ip -4 address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
2: ens32: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
inet 192.168.204.145/24 brd 192.168.204.255 scope global dynamic ens32
valid_lft 1096sec preferred_lft 1096sec
3: ens35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
inet 172.16.27.128/24 brd 172.16.27.255 scope global dynamic ens35
valid_lft 1699sec preferred_lft 1699sec
$ sudo ip netns exec host1 ip -4 address show

需要注意,在一个非默认 namespace 中运行命令需要 root 权限。你可以在 namespace 中运行任何命令,包括 ip 命令它自己,你也可以在 namespae 中运行一个交互式 shell。

列出绑定到 namespace 的所有进程

使用如下命令列出绑定到某个 namespace 的所有进程:

1
ip netns pids ${namespace name}

示例:

1
$ ip netns pid host1

标识某个进程的 primary namepsace

使用如下命令标识某个进程的 primary namespace:

1
ip netns identify ${pid}

示例:

1
$ sudo ip netns identify 1

将网络接口添加到 namespace

使用如下命令将接口添加到某个 namespace 中:

1
2
ip link set dev ${interface name} netns ${namespace name}
ip link set dev ${interface name} netns ${pid}

示例:

1
2
3
$  sudo ip link add name veth0 type veth peer name veth1
$ sudo ip link set dev veth0 netns host1
$ sudo ip link set dev veth1 netns host2

一旦你将接口添加到某个 namespace 中,它将从默认 namespace 中移除,该接口上的所有配置都将丢失,而且接口的状态也将变为 down 状态。你需要重新讲求设置为 up 并重配置。

如果你指定 PID 而不是 namespace 名,接口将添加到该 pid 对应进程的 primary namespace 中。例如通过如下方式,你可以重新将某个接口设置回默认 namespace:

1
ip netns exec ${namespace name} ip link set dev ${intf} netns 1

这里用到了一个假设:PID 1 的进程相比于其他进程,其 network namespace 更有可能是是默认 namespace。

连接两个 namespace

通过创建一个 veth pair 并将其两端分别添加到两个不同的 namespace 中,这样就可以在两个 namespace 之间建立连接。假设你想连接 default namespace host1 以及 namespace host1,可以通过如下命令实现:

1
2
3
4
5
6
7
$ sudo ip link add name veth0 type veth peer name veth1
$ sudo ip link set dev veth0 netns host1
$ sudo ip link set dev veth1 netns host2
$ sudo ip netns exec host1 ip link set dev veth0 up
$ sudo ip netns exec host2 ip link set dev veth1 up
$ sudo ip netns exec host1 ip address add 192.168.100.1/24 dev veth0
$ sudo ip netns exec host2 ip address add 192.168.100.2/24 dev veth1
1
2
3
4
5
6
7
8
9
$ sudo ip netns exec host1 ping 192.168.100.2
PING 192.168.100.2 (192.168.100.2) 56(84) bytes of data.
64 bytes from 192.168.100.2: icmp_seq=1 ttl=64 time=0.150 ms
64 bytes from 192.168.100.2: icmp_seq=2 ttl=64 time=0.037 ms
64 bytes from 192.168.100.2: icmp_seq=3 ttl=64 time=0.038 ms
^C
--- 192.168.100.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1998ms
rtt min/avg/max/mdev = 0.037/0.075/0.150/0.053 ms
1
2
3
4
5
$ sudo ip netns exec host1 ip neighbor show
192.168.100.2 dev veth0 lladdr 26:5c:22:e0:22:e5 REACHABLE
$ sudo ip netns exec host2 ip link show veth1
28: veth1@if29: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 26:5c:22:e0:22:e5 brd ff:ff:ff:ff:ff:ff link-netnsid 0

现在可以在 namespace host1 中,ping 位于 namespace host2 中的 192.168.100.2。

监控网络 namespace 子系统事件

使用如下命令,可以监控网络 namespace 子系统事件:

1
ip netns monitor

这样当出现 namespace 的创建、删除时,将会显示事件信息。

多播管理

多播通常由应用程序和路径进程使用,所以通常并不需要手动管理多播。多播相关的 ip 命令主要用于调试。

查看多播组

使用如下命令,查看多播地址:

1
2
ip maddress show
ip maddress show ${interface name}

示例:

1
2
3
4
5
$ ip maddress show
1: lo
inet 224.0.0.1
inet6 ff02::1
inet6 ff01::1

添加链路层多播地址

你无法手动加入一个 IP 多播组,但是你可以添加一个多播 MAC 地址:

1
ip maddress add ${MAC address} dev ${interface name}

示例:

1
$ sudo ip maddress add 01:00:5e:00:00:03 dev ens32

查看多播路由

多播路由无法手动添加,该命令只能显示通过路由进程添加的多播路由。单播路由管理命令使用的选项,也同样适用于该命令:

1
ip mroute show

网络事件监控

通过 iproute2,你可以监控指定的网络事件,例如网络配置的改变、路由表的改变、ARP/NDP 表的改变。

监控所有事件

使用如下命令监控所有网络事件:

1
2
ip monitor
ip monitor all

监控指定事件

使用如下命令监控指定网络事件:

1
ip monitor ${event type}

事件类型可以是:

  • link:链路状态变化,例如接口的 up/down,虚拟接口的创建/删除等
  • address:地址变化
  • route:路由变化
  • mroute:组播路由变化
  • neigh:链接表(ARP 和 NDP)的变化

另外通过 -4 或 -6 可以指定 IPv4 或 IPv6 子系统。

读取 rtmon 的日志文件

iproute2 提供的 rtmon 程序也是用于对网络事件进行监控,但是它将事件信息写入一个二进制日志文件中。ip monitor 命令可以让你读取 rtmon 的日志文件:

1
ip monitor ${event type} file ${path to the log file}

rtmon 的语法类似于 ip monitor,但是它所允许监控的事件类型只有:link、address、route 和 all,通过 -family 选项指定地址族:

1
rtmon [-family <inet|inet6>] [<route|link|address|all>] file ${log file path}

netconf(查看 sysctl 配置)

查看所有接口上的 sysctl 配置

使用如下命令查看所有接口上的 sysctl 配置:

1
ip netconf show

查看指定接口上的 sysctl 配置

使用如下命令查看指定接口上的 sysctl 配置:

1
ip netconf show dev ${interface}

示例:

1
2
$ sudo ip netconf show dev ens32
ipv4 dev ens32 forwarding off rp_filter strict mc_forwarding 0 proxy_neigh off