hcnp-ospf

ospf

一 ospf的基本概念

  ospf(open shortest path first,开放式最短路径优先)是一种典型的链路状态路由协议,是目前业内使用最广泛的IGP(内部网关协议)之一。ospf支持VLSM(可变长子网掩码),路由汇总。ospf目前主要有两个版本:ospfv2,该版本主要针对IPv4;ospfv3主要针对IPv6。

Router-ID

ospf router-id(路由器标识符) 是一个32bit长度的数值,通常使用点分十进制形式表示(与IPv4地址格式一样),用于在ospf域中唯一标识一台ospf路由器,每台ospf路由器的router-id必须全域唯一。router-id可以手工指定。如果没有手工指定,系统会自动选择设备上的一个IP地址作为router-id。强烈建议手工指定。

#为设备创建一个loopback接口,并指定接口的IP地址
[Router]int LoopBack 0
[Router-LoopBack0]ip add 1.1.1.1 32
[Router-LoopBack0]quit

#创建一个ospf进程,该ospf进程的Process-ID为1,并指定该设备的router-id为1.1.1.1(loopback0接口的地址)
[Router]ospf 1 router-id 1.1.1.1

无论是采用手工还是自动分配,一旦ospf确定了router-id后,如果再变更的话要将ospf进程重启才能使新的ospf router-id生效。重启命令如下:

<Router>reset ospf process

但是请慎用该命令。因为邻居关系会被重置,可能会造成路由重收敛。

  1. ospf:开放式最短路径优先,链路状态路由协议。

IPv4:ospfv1,ospfv2(最常用),IPv6:ospfv3

  1. 基本概念

(1)     Router-ID

1) 长度32bit的数值,与ipv4格式一致

2) 同一个域内必须唯一,在未通过手动指定时,路由器会自动选择设备上的一个ip作为router-id

3) 命令格式:ospf 1 router-id 1.1.1.1 其中ospf 1为该ospf在本路由器的进程id,只具有本地意义。变更router-id后要重启ospf进程才可生效(reset ospf process)

(2)     三张表

1) 邻居表(peer/Neighbor table):可以通过命令dis ospf peer 查看邻居,若state为full则邻居建立成功。Ospf通过在接口上周期性的交互Hello报文,来建立邻居。

2) 链路状态数据库(LSDB):通过dis ospf lsdb查看。用于存储路由器收到的LSA信息。

3) Ospf路由表:通过dis ospf routing查看。Ospf根据LSDB数据库,运行SPF算法计算出一颗以自己为根的,无环的最短路径树,从而发现到达各网段的最佳路径,得出路由信息。

(3)     度量值

1) Cost=ospf带宽参考值/接口带宽

2) 计算结果取整数部分,结果小于1,则值取1.

3) 一条ospf路径的cost值等于从目的地到本地路由器沿途所有入接口的cost总和。

4) 实际工程中,常通过手动调节cost从而影响ospf路由计算

(4)     报文类型以及格式

1)Ospf协议基于IP运行,报文采用ip封装。IP头中协议号89。

以组播地址作为目的地址;

   224.0.0.5:所有ospf路由器

   224.0.0.6:所有OSPF DR路由器

2)ospf报文头部

所有ospf报文都有统一的头部,长度24byte。

版本:对于ospfv2,该字段为2

类型:该ospf报文的类型。字段值与报文类型对应关系:1-Hello;2-DD;3-LSR;4-LSU;5-LSAck

报文长度:整个ospf报文长度

路由器ID:ospf router-id

区域ID:该报文所属区域ID,32bit数值

校验和:校验报文有效性

认证类型:报文认证类型

认证数据:报文认证内容

类型

报文名称

报文描述

1

Hello

1) 用于发现直连链路上的邻居,维护邻居关系。

2) 报文携带用于建立邻居关系的参数,两者参数匹配,方可建立邻居

3) 网络掩码:该接口的掩码,掩码不一致,无法建立邻居关系

4) Hello间隔:确保邻居之间的Hello间隔一致,否则无法建立邻居。P2P或Broadcast类型接口上该值为10s;NBMA以及P2MP类型接口上该值为30s。

5) 可选项(Options):8bit,每个比特位都用于指示该路由器的某个特定的OSPF特性。

6) 路由器优先级(router priority):也叫做DR优先级,该字段用于DR,BDR选举。缺省时OSPF接口的DR优先级为1,可手动修改。

7) 路由失效时间(router dead interval):在邻居路由器被视为无效前,需要等待收到对方Hello报文的时间。双方该值必须一致,否则无法建立邻居。该值缺省情况下为Hello interval的4倍

8) 指定路由器(DR):网络中DR的接口IP地址。若为0.0.0.0.表示网络中没有DR,或者未被选出来

9) 备份指定路由器(BDR):网络中BDR的接口IP地址。若为0.0.0.0.表示网络中没有BDR,或者未被选出来

10)            邻居:直连链路上发现的有效邻居,此处填充router-ID,若发现多个邻居,则包含多个邻居字段

2

DD(数据库描述)

1) 在路由器邻接关系建立过程中,邻居之间会交互空DD报文(不包含LSA头部信息),用来协商主从关系,router-id更大的路由器成为master路由器。

2) 主从关系确定后,双方开始使用包含着LSDB里的LSA头部的DD报文进行交互。

3) 路由器可使用多个DD报文来描述LSDB。每个DD报文都有序列号,且该序列号由Master确定。

4) 接口最大传输单元:接口的MTU缺省为0

5) 可选项:ospf可选项

6) I位:初始化位。DD报文用于协商主从关系时,该值为1;主从关系确定后,值为0.

7) M位:值为1,表示后续还有DD报文;值为0,表示这是最后一个DD报文。

8) MS位:Master将自己发送的DD报文中改值设置为1,slave设置为0。

9) DD序列号:DD报文序列号,DD报文在交互过程中序列号逐次加1,用于确保DD报文传输的有序和可靠性。DD序列号必须由master确定,slave只能使用master的DD序列号

10)            LSA头部:LSA头部信息,一个DD报文可包含多个LSA头部。

3

LSR(链路状态请求)

1) 在与ospf邻居交换DD报文后,路由器已知晓对方LSDB信息摘要,它将向对方发送LSR报文来请求所需的LSA完整数据。

2) 链路状态类型,链路状态ID,通告路由器三元组表示了所请求的LSA。LSR可能包含多个三元组。

3) 链路状态类型:本条LSA的类型。Ospf定义了多种类型的LSA,每种LSA描述ospf网络的某部分,且使用不同的类型编号。如:1-router LSA,2-network LSA,3-network summary LSA,4-ASBR summary LSA,5-AS external LSA。

4) 链路状态标识:LSA标识。不同类型的LSA该字段定义不同。

5) 通告路由器:产生该LSA的路由器的router id

4

LSU(链路状态更新)

1) 路由器以LSU报文对邻居的LSR报文进行回应,一个LSU报文可包含多个LSA

2) 当网络结构发生变化时,也可触发LSU报文泛红。

5

LSAck(链路状态确认)

1)当路由器收到邻居发过来的LSU报文后,会以LSAck进行回应。LSAck中包含了所回应LSU报文的LSA头部。

(5)     邻接关系

1) 区别邻居关系和邻接关系。两直连路由器通过hello报文发现彼此并确认双向通信后,邻居关系便形成。邻居关系建立后,还会进行其他的报文交互同时邻居状态也会不段切换,以便最终建立邻接关系。此时邻居双方状态均为Full时,网络收敛完毕。

2) 邻居状态

OSPF邻居状态

描述

Down(失效)

Ospf接口尚未收到邻居发送的hello报文

Init(初始)

A收到邻居B发来的hello报文,并未在报文中“邻居”字段发现自己的router id,A会将该邻居B状态设置为Init状态。此时两者双向通信未确立。接下来,收到hello报文的路由器A会将对方的router id 添加到自己发送的hello报文中发送给对方。

Attempt(尝试)

2-Way(双向通信)

B收到直连链路上的A发送过来的hello报文,并在报文中“邻居”字段发现了自己的router id,此时B会将该邻居状态设置为2-Way状态,表明双向通信建立。2-Way可视为ospf的稳定状态之一,是建立邻接关系的基础。

ExStart(交换初始)

该状态下,A,B双方互发空DD报文以便协商Master/Slave,router id 最大的路由器成为Master,DD序列号由其决定。

Exchange(交换)

该状态下,邻居之间交互描述自己的LSDB的DD报文(包含LSA头部)。

Loading(加载)

路由器向邻居发送LSR报文请求LSA的完整数据。对方用LSU报文回应。LSU报文里才有LSA完整信息。收到LSU报文后,使用LSAck确认。

Full(全毗邻)

该状态下,当邻居之间待请求的LSA列表为空时,表明双方LSDB同步已经完成。

(6)邻接关系建立过程

以图3-10为例讨论邻接关系建立过程

1) R1的接口激活ospf后开始发送组播的Hello报文(224.0.0.5),报文头部中,携带R1的router id以及接口的区域ID。报文荷载中携带接口的掩码,hello间隔,路由器失效间隔。邻居字段为空。

2) R2收到Hello报文后,首先会确认该报文中的参数是否与自己接口上的参数一致。若一致,R2将R1的状态设置为Init。

3) 接下来,R2在自己发送的hello报文“邻居”字段中写入R1的router id。R1收到Hello报文后,发现了自己的router id。于是将R2添加到邻居表,并把R2状态设置为2-Way。

4) 随后,R1在自己发送的Hello报文的“邻居”字段中写入R2的router id。R2收到后将邻居表中的R1状态且u安慰2-Way。至此二者形成邻居关系。

5) 如果路由器接口接入一个多路访问网络,那么随后将开始DR以及BDR选举。以BMA(广播型多路访问)网络为例,来讲一下DR,BDR选举过程。DR,BDR选举通过hello报文实现,选举发生在2-way状态后。路由器将自己接口的DR优先级填入Hello报文的“DR优先级”字段。该值缺省为1,可修改,范围0-255,为0时不具备选举资格。当某接口激活ospf后,会首先检查网络中是否有DR,若有则接受(DR不具备可抢占性)。否则拥有最高优先级DR的路由器接口当选为DR。优先级相等时,最大router id的当选DR。BDR的选举类似。我们把既非DR又非BDR的叫做DROther。MA网络中所有DROther都只和DR,BDR建立OSPF邻接关系,BDR和DR之间也建立邻接关系。DROther之间停留在2-way状态。DR负责侦听网络中拓扑变更信息并通知给其他路由器。它为网络生成Type-2 LSA。注意DR,BDR只是一个接口级别的概念。说某台路由器是DR是不严谨的。应该说某台路由器的某个接口在这个MA网络中是DR。在一个MA网络中,DR负责该网络中所有ospf路由器的LSDB同步。DR使用224.0.0.5组播地址向该网络中所有OSPF路由器发送LSU报文,所有ospf路由器都会同步自己与DR的LSDB。当DROther感知网络拓扑发生变化时,向224.0.0.6发送LSU以便通告变化,DR和BDR会侦听该组播地址。

 

 

6) 接下来,在ExStart状态下,R1和R2交互空DD报文协商主从关系如图3-11,router id大的胜出。

 

(7)区域

1) 为什么要引入域的概念?网络规模变得很大时,每台路由器所维护的LSDB变得臃肿,网络拓扑的变化会引起整个网络内的所有路由重计算,这些都会耗费许多设备资源。引入域后,LSA泛洪被限制在域内,同一个区域的路由器维护同一套相同的LSDB,每个区域独立进行SPF计算。区域内的变化局限在区域内部。如果一个路由器属于多个域,它将为每个域维护一套LSDB。

2)域的编号叫做区域ID。是一个32bit非负整数,以点分十进制形式呈现。所有非骨干区域必须与Area0相连。若有多个区域存在,则只能有一个Area0,其负责在区域之间发布路由信息。

(8)路由器角色

1) 内部路由器(IR):所有接口都接入同一个ospf区域的路由器。

2)区域边界路由器(ABR):同时接入Area0和其他区域的路由器,负责在区域间传递路由信息

3)骨干路由器(BR):所有接口都接入Area0的路由器。

4)AS边界路由器(ASBR):ospf自治域边界的路由器。ASBR会将ospf外部路由引入本域内。

3 LSA及特殊区域

(1)链路状态信息,LSDB ,SPF,无环最短路径树

(2)LSA类型。

类型

名称

描述

区域内部路由

1

路由器LSA(Router LSA)

每台ospf路由器都会产生的LSA,描述该路由器所有ospf直连接口的状况和Cost值,该LSA只能在接口所属区域内泛洪(所在广播域内泛洪)。路由器只会与DR,BDR交互该LSA。DROther之间邻接状态只会达到2-Way,不会进行LSA交互。DR,BDR,DROther会使用224.0.0.5发送hello报文;DROther会使用224.0.0.6发送LSU给DR,BDR。DR,BDR会使用224.0.0.5发送LSU给DROther;DROther会使用224.0.0.6发送LSAck,而DR,BDR使用224.0.0.5发送LSAck。

2

网络LSA(Network LSA)

由DR产生,描述该DR所接入的MA网络中所有与之形成邻接关系的路由器,其中包括DR自身,该LSA只能在接口所属区域泛洪。

区域间路由

3

网络汇总LSA(Network Summary LSA)

由ABR产生,描述到达某个区域的目标网段的路由。该类LSA主要用于区域间路由的传递

域(AS)外路由

4

ASBR汇总LSA(ASBR Summary LSA)

由ABR产生,用于描述ASBR。ASBR汇总LSA相当于一条到达ASBR的主机路由。

5

AS外部LSA(AS External LSA)

由ASBR产生,描述本AS之外的外部路由。

7

非完全末梢区域LSA(NSSA LSA)

由ASBR产生,用于描述本AS之外的外部路由。NSSA  LSA仅在产生这个LSA的NSSA内泛洪,不能直接进入骨干区域。NSSA的ABR会将7类LSA转换成5类LSA注入到骨干区域。

(3)LSA头部,一共20byte

字段

描述

链路状态老化时间(link-state age)

16bit。该条LSA老化时间。该LSA由始发路由器产生,该值为0。老化时间超过MaxAge后,给LSA不在用于路由计算

可选项(options)

8bit。每一个比特位都对应了ospf所支持的某种特性

链路状态类型(link-state type)

本条LSA类型。例如Type-1 LSA

链路状态ID(link-state id)

LSA的标识

通告路由器(advertising router)

产生该LSA的路由器的router id

链路状态序列号(link-state sequence number)

该LSA的序列号,用于判断LSA新旧或者是否重复存在。

链路状态校验和(link-state checksum)

校验和

长度(length)

LSA总字节长度

(4)LSA详解

 1)Type-1 LSA

 

对于Router LSA而言,LSa头部中的“链路状态类型”字段值为1,“链路状态ID”字段值为产生这个Type-1 LSA的路由器的Router-ID

字段

描述

V位

设置为1,表示路由器为Virtual Link的端点

E位

设置为1,表示路由器为ASBR。Stub区域内不允许出现E比特设置为1的Type-1 LSA。因此Stub区域不允许出现ASBR

B位

设置为1,表示路由器为两区域的边界路由器,即使它没有连接到Area0.

链路数量(Link Number)

Type-1 LSA描述的Link数量。路由器采用包含在Type-1 LSA内的Link来描述直连接口的。每条Link包含“链路类型”,“链路ID”,“链路数量”,“度量值”。

链路类型(Link Type)

本条Link的类型值。如下表。传输网络(TransNet);末梢网络(StubNet);点到点(P-2-P)

链路ID(Link ID)

Link的标识。如下表

链路数据(Link Data)

如下表

度量值(Metric)

Cost值

传输网络:TransNet

末梢网络:StubNet

点到点:P-2-P

如下图所示Type-1 LSA的LSU内容

 

2)      Type-2 LSA

DR会在本区域泛洪Type-2 LSA,来例举出介入该MA网络的所有路由器的Router ID(包括DR自身),以及这个网络的掩码。因此Type-2 LSA仅存在于拥有MA网络的区域中,且由DR产生。

 

在Type-2 LSA头部中“链路状态类型”字段为2,“链路状态ID”字段为产生这个Type-2 LSA的DR的接口IP地址。

网络掩码:该MA网络的网络掩码

相连的路由器(Attached  Router)的Router-ID:连接到该MA网络的路由器的Router-ID(与该DR建立了邻接关系的邻居的Router-ID,以及DR自己的Router-ID),如果有多台路由器接入该MA网络,则使用多个字段描述。

经过Type-1和Type-2的泛洪,ospf就能够描绘出一个区域内的完整拓扑。

如下图所示Type-2 LSA的LSU内容

 

3) Type-3 LSA

Type-3 LSA(Network Summary LSA):网络汇总LSA。和路由汇总完全是两个不同的概念。其由ABR产生,他向某区域注入Type-3 LSA 以便该区域能够计算出达到其他区域的域间路由。报文格式如下:

 

“链路状态ID”字段为区域间路由的目的网络地址。

网络掩码:区域间路由的目的网络掩码

度量值:路由的cost

如下图所示Type-3 LSA的通告过程

 

报文格式如下。

 

4) Type-4 LSA

Type-4 LSA被称为ASBR汇总LSA,由ABR产生,实际上是一条到达ASBR的主机路由。格式与Type-3 LSA一致。

 

5) Type-5 LSA

当ASBR将外部路由引入ospf时,会产生Type-5 LSA用于描述外部路由。这种类型的LSA一旦产生,会在整个ospf域内传播(除一些特殊区域)。

 

“链路状态ID”字段值是外部路由的目的网络地址。

网络掩码:外部路由的网络掩码

E位:表示该外部路由的度量值类型。Ospf定义了两种外部路由的度量值类型,Metric-Type-1和Metric-Type-2。该比特位为0时,表示外部路由使用度量值类型为Metric-Type-1;为1时使用的为Metric-Type-2。

度量值(Metric):外部路由的Cost

转发地址(Forwarding Address,FA):FA为0.0.0.0时,则到达该外部网段的流量会被发往引入该外部路由的ASBR。不为0.0.0.0时,则流量会被发往这个转发地址。

外部路由表记(External Route Tag):只有外部路由才能够携带该标记,常被用于部署路由策略。缺省为1。

 

6) Type-7 LSA

即非完全末梢区域外部LSA。一种特殊的LSA,用于描述ospf外部路由,报文格式与Type-5 LSA一致,但是他的泛洪范围却是有严格限制的,他只能在NSSA(Not-So-Stubby Area)区域内泛洪,不能进入Area0。

(6)     区域类型以及详解

1) 骨干区域

骨干区域是Area0,是整个ospf域的中心枢纽。一个ospf域有且只有一个Area0,所有的区域间路由必须通过Area0中转。

2) 常规区域

Ospf要求所有非骨干区域都必须与Area0直接相连。常规区域允许Type-1,2,3,4,5 LSA泛洪。

3) 末梢区域(Stub Area)

末梢区域也叫Stub区域,当一个非0常规区域只有单一出口(例如该区域只有一个ABR),或者区域内的路由器不需要根据特定的外部路由来选择离开区域的出口时,该区域可被配置为Stub区域。该区域禁止外部路由(Type-5 LSA)被发布到该区域以及禁止禁止外部路由的重发布,通过这种方式可减少区域内所泛洪的LSA数量。同时该区域的ABR自动下发一条使用Type-3 LSA描述的默认路由,是的区域内的路由器都能够通过这条默认路由到达域外。在一个大量引入外部路由的ospf网络中,适当的将区域配置为Stub,可减少区域内的路由器的路由表项规模,降低设备资源消耗。当然还可在Stub的基础上进一步减少LSA的泛洪,即在Stub区域的ABR上进一步阻挡描述区域间路由的Type-3 LSA进入该区域,区域内的路由器通过ABR向该区域下发的默认路由到达本区域之外的其他区域以及域外的网络,这种特殊区域叫做完全末梢区域(Totally Stub Area)。

 

4)非完全末梢区域。

该特殊区域,一方面可以阻挡Type-4 ,5 LSA的进入,另一方面允许路由器引入少量外部路由,并且以Type-7 LSA描述。这些Type-7 LSA只可在当前的NSSA内泛洪,不允许直接进入Area0,NSSA的ABR会将这些Type-7 LSA转换成Type-5 LSA然后注入Area0,从而泛洪到整个ospf区域。当然还可在NSSA的基础上进一步减少LSA的泛洪,即在NSSA区域的ABR上进一步阻挡描述区域间路由的Type-3 LSA进入该区域,区域内的路由器通过ABR向该区域下发的默认路由到达本区域之外的其他区域以及域外的网络,这种特殊区域叫做完全末梢区域(Totally NSSA)。

 

 

(7)     LSA新旧

1) ospf以1800s为周期对LSA进行泛洪

2) ospf采用链路状态类型,链路状态ID,通告路由器三元组来标识一个LSA。

3) 判断新旧:1.首先拥有更高链路状态序列号的LSA实例被认为更新,因为路由器每次在刷新LSA的时候,会将LSA的链路状态序列号加1。2.如果LSA实例中的链路状态序列号相同,那么拥有更大校验和的LSA实例更新。3.如果前两者相同,则

  3  ospf协议特性

(1)     路由汇总

1) 背景:网络规模大,路由表变得臃肿,消耗设备资源。泛洪严重。

2) Ospf路由汇总需要手工部署,不支持自动汇总。有两种部署方法,一种需要在ABR上部署,一种需要在ASBR上部署。

3) 在ABR上部署路由汇总

 

使用abr-summary命令进行路由汇总时,只能在ABR上部署,而且只能够对ABR直连区域的区域内部路由进行汇总。可增加cost关键字指定汇总路由的Cost值,缺省时,汇总路由的Cost值等于被汇总路由的明细路由的Cost值中最大的那个。

4) 在ASBR上部署路由汇总

采用asbr-summary命令执行路由汇总操作只能在ASBR上进行才会生效,而且只针对ASBR引入的外部路由生效。可增加cost关键字指定汇总路由的Cost值,缺省时如果明细路由的度量值类型为Metric-Type-1,那么汇总路由的cost等于明细路由的Cost中的最大值;如果明细路由的度量值类型为Metric-Type-2,那么汇总路由的cost等于明细路由的cost最大值加1.

(2)     Virtual Link

Ospf virtual link 是一种虚拟的,逻辑的链路,被部署在两台ospf路由器之间,它穿越某个非骨干区域,用于实现另一个非骨干区域与Area0相连。虚链路建立完成后,这两台路由器开始在虚链路上建立邻接关系,同时会产生Type-1 LSA来描述虚链路,采用类型4的link描述。Virtual link 不能被部署在Stub区域内。

Type-5 LSA不会通过Virtual link传播。

(3)     默认路由

1) 使用 import route-static引入默认路由

2) 在Stub区域中ABR自动下发默认路由

3) 在Totally Stub中ABR自动下发默认路由

4) Totally NSSA中ABR自动下发默认路由

5) Totally NSSA中ABR自动下发默认路由

(4)     报文认证

在ospf报文头部中,几个与认证相关的字段用于实现报文认证功能。Ospf支持三种类型的认证方式,空认证(Null Authentication),简单口令认证(simple password),密文认证(ctyptograhpic Authentication),对应的认证类型字段分别为0,1,2。

   

空认证:意味着对接口的ospf报文的收发不做认证,认证字段类型值为0。

简单口令认证:即明文认证,明文口令包含在ospf报文中收发,不安全。认证字段类型值为1。

密文认证:ospf报文中不直接包含明文口令,而是包含一个使用口令的Hash值。认证字段类型值为2。

(5)     ospf防环机制

纠正错误认知:每个Area内的路由器的LSDB相同,这得益于Type-1和Type-2 LSA的泛洪。不同Area内的路由器的LSDB不同,很明显,Area1内的路由器只会包含Area0的汇总路由,Area0只会将Type-3 LSA发布到Area1内,而Type-3 LSA不含Area0内具体的接口。

1) 区域内部路由放环

每个Area内的路由器都能够根据域内的Type-1和Type-2 LSA,运用SPF算法,计算出到达每个网段的最优路径,并将这些路径加载到路由表中,英雌区域内的路由可以实现无环路。

2) 区域间路由防环

1) ospf要求所有非骨干区域必须与Area0直接相连,区域间路由需经由Area0中转,这个规则使区域间路由不能在两个非骨干区域之间传递,这在很大程度上规避了区域间路由环路的发生,是的ospf区域架构在逻辑上形成一个星型结构拓扑图。

2) ABR从非骨干区域收到的Type-3 LSA不能用于区域间路由的计算。

3) ABR只能将自己到达所连接区域的区域内部路由注入骨干区域(区域间路由则不被允许),另外,可以将其到达所连接区域的区域内部路由以及到达其他区域的区域间路由注入非骨干区域。

4) ABR不会将描述到达某个区域内网段路由的Type-3 LSA再注入回该区域。

5) Type-3 LSA还设计了Down-Bit(一个特殊比特位),用于在MPLS VPn环境下进行路由放环。

          

猜你喜欢

转载自www.cnblogs.com/guang-li/p/12128618.html