标签 routeros 下的文章

RouterOS单系统实现透明网关

20231218更新:优化hysteria启动脚本。

自从接触mikrotik公司的RouterOS系统以来,一直都想以routeros单系统实现内外网自动分流、dns防污染的透明网关。
由于routeros是商业公司开发的闭源路由系统,虽然是基于linux内核,但是并不像openwrt一样可以安装任意软件。
所以在v6版本时代尝试过ipsec、ipip、gre隧道等方式穿墙,效果并不是很好,要么会受到墙的干扰,要么运营商qos太厉害,
要么协议本身限制再公网跑不起速度来。

所以引入了openwrt旁路由,routeros导入国内IP地址表,通过rooting mark的方式实现国内外IP地址分流,把国外IP地址转发给openwrt,openwrt通过ssr+或者passwall等使用ss、ssr、trojen、vless、hysteria等穿墙协议实现国外流量送出墙。

由于openwrt自身的复杂、开源软件质量,以及编译时的细节选项的各种问题,导致openwrt并不是十分稳定,且排查较为困难。
一直认为使用openwrt作为旁路由并不完美。后来routeros系统增加了wireguard,选择合适线路的vps后,可以实现勉强能做到单系统的透明网关,但是最近一段时间wireguard被墙阻断越来越严重,更换端口勉强能用几天。
再后来routeros内置了容器功能,极大的增大了发挥空间,虽然是个严重严格的容器功能。

通过以容器方式起mosdns实现了dns防污染、IPv4和IPv6自定义优先级等功能。
最近研究了一些前人的教程方法后,实现了容器内clash、sing-box、hysteria等程序路由功能。

RouterOS容器实现路由方式穿墙的方法,以hysteria这个简单程序为例。
1,通过hub.docker.com远程仓库拉去镜像或者导入tar文件方式创建容器。
以tar文件方式为例
电脑安装docker desktop并启动
命令行

docker pull alpine
docker save -o alpine.tar alipine

如果routeros是arm版的话

docker pull alpine@sha256:30e6d35703c578ee703230b9dc87ada2ba958c1928615ac8a674fcbbcbb0f281
#sha256:30e6d35703c578ee703230b9dc87ada2ba958c1928615ac8a674fcbbcbb0f281可在docker hub的网页上找到

2,然后把tar文件上传到routeros,建议开启smb功能,通过smb新建的文件权限比sftp或者scp要高
3,然后是创建容器,详细参考官方文档
创建完后,修改cmd属性

tail -f /dev/null

然后启动容器
4,通过shell 0 容器编号进入容器字符界面
使用wget命令下载hysteria,并解压
创建hysteria的配置文件,供参考,以服务端配置为准

{
  "insecure": true,
  "protocol": "wechat-video",
  "down_mbps": 330,
  "server": "x.x.x.x:x",
  "tun": {
  "name": "tun0"
  },
  "auth_str": "xxxx",
  "disable_mtu_discovery": false,
  "up_mbps": 110,
  "alpn": "h3",
  "server_name": "wechat.com"
}

5,然后创建tun

apk update
apk add iproute2
ip tuntap add mode tun dev tun0
ip addr add 198.18.0.1/15 dev tun0
ip link set dev tun0 up

6,修改路由,可以通过修改默认路由metric方式
假设 eth0 是原本的网卡,网关地址为 192.168.1.1,x.x.x.x是vps的IP地址

ip route del default
ip route add default via 198.18.0.1 dev tun0 metric 1
ip route add default via 192.168.1.1 dev eth0 metric 10
ip route add 192.168.0.0/16 via 192.168.1.1
ip route add 10.0.0.0/8 via 192.168.1.1
ip route add x.x.x.x via 192.168.1.1

也可以通分解0.0.0.0/0方式添加路由

ip route add 0.0.0.0/1 via 198.18.0.1
ip route add 128.0.0.0/1 via 198.18.0.1
ip route add 192.168.0.0/16 via 192.168.1.1
ip route add 10.0.0.0/8 via 192.168.1.1
ip route add x.x.x.x via 192.168.1.1

7,退出容器,停止容器
修改容器cmd参数

/root/hysteria client --config /root/hk.json

启动容器
8,配置routeros转发国外流量给容器,参考Routeros V7配置策略路由

9,容器内hysteia一键启动脚本start.sh

#!/bin/sh
GW="192.168.1.1"
cp /hysteria/hysteria /bin/
ip route replace 10.0.0.0/8 via $GW
ip route replace 192.168.0.0/16 via $GW
awk -F'"|:' '/"server"/ {cmd="ip route replace "$5" via '$GW'";system(cmd)}' /hysteria/$1
sleep 2&&ip addr add 198.18.0.1 dev tun0&&ip link set dev tun0 up&&ip route replace default via 198.18.0.1 &
hysteria client --config /hysteria/$1

在routeros上传hysteria可执行文件、hysteria配置文件和上面启动脚本到/container/etc/hysteria里
routeros新建容器命令

/container/mounts add name="hysteria" src="/container/etc/hysteria" dst="/hysteria"
/container add file=container/tmp/alpine_v3.18.2.tar interface=container-hysteria hostname=hysteria mounts=hysteria root-dir=container/root-dir/hysteria cmd="sh /hysteria/start.sh hk.json" start-on-boot=yes
/container start [find hostname=hysteria]

说明
由于routeros的容器功能细节一直在变动,有时候上一个版本还能使用,更新之后容器就不能启动了,而且日志一点信息都没有,所以采取了最保守的方式手动配置容器内应用的方式。
容器的网卡配置既不在容器内部存储,也不在routeros的veth里存储,似乎在一个容器外部不可见的地方,只能在容器内部操作,删除重建容器使用原来veth发现原有tun、静态路由居然还在。在routeros里修改veth的ip地址,居然不会同步修改容器内IP地址。
7.6版能启动的openwrt容器,新版就不能启动,一点日志都没有。
之前容器内可以写入mount的目录,新版就没有写权限,也没有执行权限,虽然容器内显示有权限。

也研究了tinyserve的bird+clash容器实现ospf动态路由透明网关方式,他使用了s6在容器内看护了几个应用,导致容器内存需求较大,小于4G内存的就别测试了。我使用rb5009,内存只有1gb,遇到routeros内存不足重启的问题。
动态路由这种透明网关方式早在ssr出现之前就有人提出,使用树莓派上跑bird+ss实现,但是我一直觉得一旦穿墙失效,所有数据包都会被运营商和墙捕捉到,为了高可用牺牲安全性不太值得,所以一直没用。

RouterOS在ESXi8.0上以UEFI方式引导启动

最近新装了虚拟化平台esxi8.0b,在创建routeros虚拟机时遇到了一系列问题:
1,esxi8.0b默认创建的ESXi 8.0(虚拟硬件版本20)版本虚拟机使用了新的api,默认以uefi引导系统,在创建完成后不能修改为bios,报错“ACPI motherboard layout requires EFI”。
2,RouterOS的CHR版磁盘镜像默认以bios引导。使用x86的iso可以正常安装使用uefi引导虚拟机,但是系统显示的是x86架构,通过升降级不能切换架构,x86架构不启用open-vm-tools服务,这个服务不启动,在esxi控制面板看不到hostname和IP地址等信息。此外x86架构和chr架构的授权和有些功能也不相同。
3,RouterOS的CHR版磁盘镜像有两个分区,第一个是efi分区,文件系统是ext2,而非uefi标准的fat。
4,RouterOS的CHR版磁盘镜像有独立的mbr和gpt分区表,既不是gpt也不是Hybrid MBR。
5,RouterOS的CHR版uefi启动不支持Secureboot。

以下是让routeros v7.8在ESXi8.0上以UEFI方式引导启动的方法,此方式只在routeros v7.8chr版测试通过,不确定后续版本升级是否可用。
1,创建没有硬盘的虚拟机,关闭secureboot。
2,下载routeros的vmdk虚拟机硬盘文件,解压上传到esxi。
3,ssh登录到esxi,转换routeros的vmdk磁盘格式,并扩展磁盘空间。

vmkfstools -i chr-7.8.vmdk routeros.vmdk -d thin
vmkfstools -X 20G routeros.vmdk

4,修改虚拟机配置,添加刚才生成的vmdk磁盘。
5,虚拟机挂载ubuntu的live-cd或其他有gdisk命令的系统iso到虚拟机。
6,开机进入ubuntu桌面,备份RouterOS Boot分区的全部文件
7,取消挂载,并使用fat格式化第一个分区

mkfs -t fat /dev/sda1

8,恢复步骤6中备份的文件到第一个分区后取消挂载routeros的两个磁盘
9,使用gdisk创建Hybrid MBR分区表,过程如下:

root@ubuntu:/home/ubuntu# gdisk /dev/sda
GPT fdisk (gdisk) version 1.0.8

Partition table scan:
  MBR: MBR only
  BSD: not present
  APM: not present
  GPT: present

Found valid MBR and GPT. Which do you want to use?
 1 - MBR
 2 - GPT
 3 - Create blank GPT

Your answer: 2
Using GPT and creating fresh protective MBR.

Command (? for help): t
Partition number (1-2): 1
Current type is EF00 (EFI system partition)
Hex code or GUID (L to show codes, Enter = EF00): 8300
Changed type of partition to 'Linux filesystem'

Command (? for help): r

Recovery/transformation command (? for help): h

WARNING! Hybrid MBRs are flaky and dangerous! If you decide not to use one,
just hit the Enter key at the below prompt and your MBR partition table will
be untouched.

Type from one to three GPT partition numbers, separated by spaces, to be
added to the hybrid MBR, in sequence: 1 2
Place EFI GPT (0xEE) partition first in MBR (good for GRUB)? (Y/N): n

Creating entry for GPT partition #1 (MBR partition #1)
Enter an MBR hex code (default 83): 
Set the bootable flag? (Y/N): y

Creating entry for GPT partition #2 (MBR partition #2)
Enter an MBR hex code (default 83): 
Set the bootable flag? (Y/N): n

Unused partition space(s) found. Use one to protect more partitions? (Y/N): n

Recovery/transformation command (? for help): w
Warning! Secondary header is placed too early on the disk! Do you want to
correct this problem? (Y/N): y
Have moved second header and partition table to correct location.

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GPT) to /dev/sda.
The operation has completed successfully.

10,重启系统即可以UEFI方式启动RouterOS CHR

附:
初始vmdk的mbr分区表:

root@ubuntu:/home/ubuntu# gdisk /dev/sda
GPT fdisk (gdisk) version 1.0.8

Partition table scan:
  MBR: MBR only
  BSD: not present
  APM: not present
  GPT: present

Found valid MBR and GPT. Which do you want to use?
 1 - MBR
 2 - GPT
 3 - Create blank GPT

Your answer: 1

Command (? for help): p
Disk /dev/sda: 262144 sectors, 128.0 MiB
Model: VMware Virtual I
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): F1C07EE3-729F-4E5F-8C77-AEC9BC697BD3
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 262110
Partitions will be aligned on 2-sector boundaries
Total free space is 4063 sectors (2.0 MiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1              34           65569   32.0 MiB    8300  Linux filesystem
   2           65570          258047   94.0 MiB    8300  Linux filesystem

Command (? for help): q

初始vmdk的gpt分区表:

root@ubuntu:/home/ubuntu# gdisk /dev/sda
GPT fdisk (gdisk) version 1.0.8

Partition table scan:
  MBR: MBR only
  BSD: not present
  APM: not present
  GPT: present

Found valid MBR and GPT. Which do you want to use?
 1 - MBR
 2 - GPT
 3 - Create blank GPT

Your answer: 2
Using GPT and creating fresh protective MBR.

Command (? for help): r

Recovery/transformation command (? for help): p
Disk /dev/sda: 262144 sectors, 128.0 MiB
Model: VMware Virtual I
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): 197E76F1-9677-0D46-9C33-F00032DDDBB1
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 258047
Partitions will be aligned on 2-sector boundaries
Total free space is 0 sectors (0 bytes)

Number  Start (sector)    End (sector)  Size       Code  Name
   1              34           65569   32.0 MiB    EF00  RouterOS Boot
   2           65570          258047   94.0 MiB    8300  RouterOS

Recovery/transformation command (? for help): q

创建hybrid mbr后的vmdk的gpt分区表:

Recovery/transformation command (? for help): p
Disk /dev/sda: 262144 sectors, 128.0 MiB
Model: VMware Virtual I
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): 197E76F1-9677-0D46-9C33-F00032DDDBB1
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 258047
Partitions will be aligned on 2-sector boundaries
Total free space is 0 sectors (0 bytes)

Number  Start (sector)    End (sector)  Size       Code  Name
   1              34           65569   32.0 MiB    8300  RouterOS Boot
   2           65570          258047   94.0 MiB    8300  RouterOS

Recovery/transformation command (? for help): o

Disk size is 262144 sectors (128.0 MiB)
MBR disk identifier: 0x00000000
MBR partitions:

Number  Boot  Start Sector   End Sector   Status      Code
   1      *             34        65569   primary     0x83
   2                 65570       258047   primary     0x83
   3                     1           33   primary     0xEE

参考:
1,Router OS 7 on UEFI
2,Hybrid MBR(MBR 混合分区表)
3,Manual:CHR VMWare installation

RouterOS使用DNS转发功能实现域名分流解析

20220718更新:更新域名匹配正则,添加每周更新脚本。由于缓存查询时间增加过多,此方法不推荐,详情参考文章后部。

之前写过两篇文章介绍实现域名分流解析的方案,一个是使用dnsmasq基于大陆域名白名单分流解析域名,另一个是使用overture替换dnsmasq做域名分流解析
这次介绍的方案与dnsmasq类似,默认使用境外dns服务器解析,境内域名使用白名单转发到境内dns服务器。
routeros在v6.47中增加了静态条目规则type属性,其中包含FWD类型。FWD类型可以将指定域名转发到指定的DNS服务器。
以下以routeros v7.3.1的chr版本为环境,dns服务器是8.8.8.8和8.8.4.4
增加alicdn.com域名及子域名转发给114.114.114.114解析。正则匹配如下

^(.*\.)?alicdn\.com$

命令如下

/ip/dns/static/add regexp="^(.*\\.)?alicdn\\.com\$" forward-to=114.114.114.114 ttl=300s

详细命令请参考官方wiki
这里使用dnsmasq-china-list做白名单转发给境内dns服务器解析,生成脚本如下

/ip/dns/static/remove [/ip/dns/static/find type=FWD]
/ip/dns/static/add regexp="^(.*\\.)?0-100\\.com\$" forward-to=114.114.114.114 ttl=300s
/ip/dns/static/add regexp="^(.*\\.)?0-6\\.com\$" forward-to=114.114.114.114 ttl=300s
/ip/dns/static/add regexp="^(.*\\.)?00\\.net\$" forward-to=114.114.114.114 ttl=300s
/ip/dns/static/add regexp="^(.*\\.)?000\\.link\$" forward-to=114.114.114.114 ttl=300s
/ip/dns/static/add regexp="^(.*\\.)?00000\\.host\$" forward-to=114.114.114.114 ttl=300s
/ip/dns/static/add regexp="^(.*\\.)?00042\\.com\$" forward-to=114.114.114.114 ttl=300s
/ip/dns/static/add regexp="^(.*\\.)?00058\\.com\$" forward-to=114.114.114.114 ttl=300s
/ip/dns/static/add regexp="^(.*\\.)?0006266\\.com\$" forward-to=114.114.114.114 ttl=300s
/ip/dns/static/add regexp="^(.*\\.)?0007\\.net\$" forward-to=114.114.114.114 ttl=300s
...

脚本有接近7万行,使用intel i5-7200u分配2逻辑核心的虚拟机首次执行需要约3.5分钟,阿里云1核心服务器首次执行需要2.5分钟,再次执行需要7.5分钟,再次执行时会先删除type=FWD的静态条目,脚本执行时cpu占用100%,所以cpu性能较差时需要耐心等待。

这里下载每周自动生成的脚本。
导入方法:使用

/import dns.rsc

导入,等待完成。

使用RouterOS自带dns转发功能做域名分流解析优点是可以抛弃openwrt或者linux服务器的小尾巴,实现routeros单一系统完成全部网络功能,特别适合不支持直通的cpu裸装routeros的情况。
缺点和使用dnsmasq分流解析一样,境内域名列表更新不及时,可能导致一些新的域名解析成境外地址。

另外,导入dns转发脚本后不要使用webfig打开dns静态列表,条目太多会卡死。
附:自动生成dns.rsc脚本

#!/bin/bash
echo "/ip/dns/static/remove [/ip/dns/static/find type=FWD]">/tmp/dns.rsc && curl -s https://raw.githubusercontent.com/felixonmars/dnsmasq-china-list/master/accelerated-domains.china.conf|sed '/#/d'|sed 's/\/114.114.114.114//g'|sed 's/\./\\\\./g'|sed 's/server=\//\/ip\/dns\/static\/add regexp=\"^(.*\\\\.)?/g'|sed 's/$/&\\$\" forward-to=114.114.114.114 ttl=300s/g'>>/tmp/dns.rsc

发现问题:
1,routeros的dns本地缓存查询时间显著增加,使用dig命令发现未导入时缓存查询时间小于1ms,导入后缓存查询时间约28ms,白名单域名反而查询时间更短。
2,在dns设置增加缓存到1gb后观察,发现导入后占用约180mb缓存。另可以通过观察缓存大小来大致确定导入进度。

Routeros V7配置策略路由

20230529更新:增加ipv6策略路由实现境内外ipv6地址分流的方法。

在两年前我写过一篇routeros配置vpn分流大陆ip的文章,里面主要介绍了使用routeros基于源地址和目的地址的策略路由。在使用Routeros V7 测试版的过程中发现V7相比于V6版在路由配置方便有很大变更,所以这篇文章会以Routeros V7为基础介绍策略路由的配置。

网络基本情况
routeros作为家庭唯一路由,通过pppoe接入互联网,通过wireguard连接香港的routeros。

策略路由目标
1,需要翻墙的内网ip:目的地址是境内ip走pppoe默认路由,目的地址是境外IP走wireguard通过香港routeros进入墙外互联网
2,其他内网ip全部走pppoe默认路由

IP地址分配参考上一篇文章Routeros 配置WireGuard

本地lan的地址是192.168.1.1/24,wg-access地址是10.0.0.1/24,wg-hk的地址是10.0.1.1/30,手机wireguard的地址是10.0.0.2/32,笔记本电脑的地址是10.0.0.3/32
香港wg0地址是10.0.1.2/30

配置步骤
1,导入大陆IP地址表
参考Routeros的address-list大陆分流列表
把cnip.rsc上传到routeros,执行

/import cnip.rsc

2,配置分流路由表
Routeros V7取消了通过firewall的mangle里添加路由标记自动添加路由表功能,所有这里需要手动添加路由表

/routing/table/add name="gfw" fib

添加IP分流策略路由

/ip/route/add dst-address=0.0.0.0/0 routing-table="gfw" gateway=10.0.1.2

3,新建需要翻墙的内网地址列表,假定192.168.1.100和192.168.1.200需要翻墙

/ip/firewall/address-list/add list=gfw address=192.168.1.100/32
/ip/firewall/address-list/add list=gfw address=192.168.1.200/32

4,给需要翻墙的内网ip添加标记

/ip/firewall/mangle/add chain=prerouting action=mark-routing new-routing-mark=gfw passthrough=yes dst-address-type=!local src-address-list=gfw dst-address-list=!CNIP log=no log-prefix=""

5,配置无污染DNS服务器
参考使用dnsmasq基于大陆域名白名单分流解析域名使用overture替换dnsmasq做域名分流解析

6,测试目的IP分流
在192.168.1.100上执行traceroute命令

traceroute 8.8.4.4
192.168.1.100--->192.168.1.1--->10.0.1.2--->香港服务器公网路由--->8.8.4.4
traceroute 114.114.114.114
192.168.1.100--->192.168.1.1--->PPPoE公网路由---->114.114.114.114

ps,按照上述ipv4的策略路由配置方法,ipv6并不会生效。
此问题是ipv6策略路由的路由表问题。截至v7.10beta8,mikrotik还没有修复ipv6的路由表问题,或者是mikrotik将来会调整策略路由配置方法。
ipv6的firewall的对应mangle条目的counter有计数,说明修改routing-mark已经生效,但是数据包没有走routing-mark设置的路由表。
经过搜索mikrotik官方论坛,需要另外增加一条设置

/routing/rule/add action=lookup-only-in-table routing-mark=gfw table=gfw

Routeros 配置WireGuard

Routeros在2019年下半年发布了V7的beta版,我在之前文章一直在更新beta版新特性。2020年6月routeros v7beta7系统内核升级到5.6,8月发布了Routeros v7.1 beta2增加了对WireGuard的支持。
WireGuard被视为下一代VPN隧道协议,是类似于gre、ipip隧道的无状态VPN隧道,基于UDP协议,配置简单。

由于Routeros V7还在beta阶段,webfig和winbox等UI控制台还存在很多不完善的地方,下面通过命令行配置。
以下配置基于Routeros V7.1beta2 CHR版。

简单介绍一下拓扑,routeros作为唯一路由器,新建两个wireguard接口:wg-access和wg-hk,wg-access用于手机、PC等在非本地网络下的远程接入,wg-hk用于连接香港VPS用于加速访问境外地址。

本地lan的地址是192.168.1.1/24,wg-access地址是10.0.0.1/24,wg-hk的地址是10.0.1.1/30,手机wireguard的地址是10.0.0.2/32,笔记本电脑的地址是10.0.0.3/32
香港wg0地址是10.0.1.2/30

通过ssh连接routeros或者webfig、winbox的terminal输入命令。
1,wg-access
新建本地routeros的wg-access接口,注意mtu要改成1500,默认是1420,输入下面新建接口命令后会自动创建接口的private-key和对应的public-key

/interface/wireguard/add name="wg-access" mtu=1500 listen-port=50000

给本地routeros的wg-access接口配置IP地址

/ip/address/add address=10.0.0.1/24 interface=wg-access

添加wg-access接口下的客户端peers,注意下面的public-key和preshared-key替换成实际的key,客户端的key在新建空隧道时会自动生成

/interface/wireguard/peers/add interface=wg-access public-key="lYVc...AlE=" allowed-address=10.0.0.2/32 preshared-key="93E...PSA="
/interface/wireguard/peers/add interface=wg-access public-key="lYVc...AlE=" allowed-address=10.0.0.3/32 preshared-key="93E...PSA="

2,wg-hk
新建本地routeros的wg-hk接口

/interface/wireguard/add name="wg-hk" mtu=1500 listen-port=10000

新建香港VPS上routeros的wg0接口

/interface/wireguard/add name="wg0" mtu=1500 listen-port=10000

在香港routeros的wg0接口下添加peer

/interface/wireguard/peers/add interface=wg0 public-key="lYVc...AlE=" allowed-address=10.0.1.1/30 preshared-key="93E...PSA="

给香港routeros的wg0接口配置IP地址

/ip/address/add address=10.0.1.2/30 interface=wg0

在香港routeros上添加本地lan的路由

/ip/route/add dst-address=192.168.1.1/24 gateway=10.0.1.1

在本地routeros的wg-hk接口下添加peer

/interface/wireguard/peers/add interface=wg-hk public-key="lYVc...AlE=" endpoint=香港VPS的公网IP:10000 allowed-address=0.0.0.0/0 preshared-key="93E...PSA="

给本地routeros的wg-hk接口配置IP地址

/ip/address/add address=10.0.1.1/30 interface=wg-hk

配置本地routeros的策略路由
参考Routeros V7配置策略路由routeros配置vpn分流大陆ip

关于routeros的wireguard的说明
1,mtu要修改成1500,使用默认的1420时会有部分cdn不能加载,现象是浏览器报超时错误。mtu问题类似情况参考ikev2 客户端mtu引起的网络故障
2,wireguard每个接口有个自己的公钥对应的路由表,配置peer就是写路由表,routeros v7.1beta2目前有bug,配置多个相同路由的peer,删除后配置的不会恢复之前的路由。
3,routeros v7.1beta2的全局路由表和wireguard的路由表可能存在某些bug,如果配置后不生效,可以删除peer后重新添加。