随着互联网和各类园区网络的快速发展,网络规模和复杂度不断增加,这对路由协议提出了更高的要求。传统的路由协议如RIP已无法满足大规模网络的需求,而开放最短路径优先(OSPF)协议因其高效、稳定和可扩展性成为大型复杂网络的首选。OSPF是一种基于链路状态的内部网关路由协议,具备路径选择无环路、收敛速度快、支持大规模网络和复杂拓扑等优点。因此,深入研究OSPF协议,不仅有助于理解其工作原理和特性,还有助于提高园区网络的设计和管理效率。
OSPF协议原理及其特性分析
OSPF协议简介
开放式最短路径优先(Open Shortest Path First,OSPF)是对链路状态路由协议的一种实现,隶属内部网关协议(IGP),故运作于自治系统内部。OSPF协议是大中型网络上使用最为广泛的IGP(Interior Gateway Protocol)协议。采用迪杰斯特拉算法(Dijkstra’s algorithm)来计算最短路径树。它使用“代价(Cost)”作为路由度量。链路状态数据库(LSDB)用来保存当前网络拓扑结构,路由器上属于同一区域的链路状态数据库是相同的(属于多个区域的路由器会为每个区域维护一份链路状态数据库)。
OSPF最主要的特征是使用分布式的链路状态协议(link state protocol),而不是相RIP(路由信息协议)那样的距离向量协议。OSPF向本自治系统的所有路由器发送信息。这里是用的方法是洪泛法(flooding),这就是路由器通过所有输出端口向所有相邻的路由器发送信息。而每一个相邻路由器又再将此信息发往其所有的相邻路由器(不再发送给刚刚发来信息的那个路由器)。这样,最终整个区域中所有的路由器都得到了这个信息的一个副本。
发送的信息就是与本路由器相邻的所有路由器的链路状态,但这只是路由器所知道的部分信息。所谓“链路状态”就是说明本路由器和哪些路由器相邻,以及该链路的“度量”(metric)。OSPF将这个“度量”用来表示费用、距离、时延、带宽,等等。这些都是由网络管理人员来决定。因此较为灵活。
OSPF提出了“区域(Area)”的概念,一个网络可以由单一区域或者多个区域组成。其中,一个特别的区域被称为骨干区域(Backbone Area),该区域是整个OSPF网络的核心区域,并且所有其他的区域都与之直接连接。所有的内部路由都通过骨干区域传递到其他非骨干区域。所有的区域都必须直接连接到骨干区域,如果不能创建直接连接,那么可以通过虚拟链路(Virtual-link)和骨干区域创建虚拟连接。
同一个广播域(Broadcast Domain)的路由器或者一个点对点(Point To Point)连接的两端的路由器,在发现彼此的时候,创建邻接(Adjacencies)。多路访问网络以及非广播多路访问网络的路由器会选举指定路由器(Designated Router,DR)和备份指定路由器(Backup Designated Router,BDR),DR和BDR作为网络的中心负责路由器之间的信息交换从而降低了网络中的信息流量。OSPF协议同时使用单播(Unicast)和组播(Multicast)来发送Hello包和链路状态更新(Link State Updates),使用的组播地址为224.0.0.5和224.0.0.6。OSPF协议不使用TCP或者UDP协议而是承载在IP协议之上,IP协议号为89,工作在OSI模型的传输层。节点在创建邻接,接受链路状态通告(Link-state Advertisement,LSA)时,可以通过MD5或者明文进行安全验证。
OSPF的主要特性
OSPF协议以其高效、稳定和灵活性而著称。其主要特性包括:
- 链路状态协议:通过交换链路状态信息,每个路由器生成全网拓扑图,计算最短路径。
- 无环路:利用SPF算法确保路径无环路,避免路由环路问题。
- 快速收敛:增删节点时,只需局部调整链路状态,无需全局重新计算。
- 区域划分:支持多区域划分,降低链路状态更新的复杂性和频率。
- 可扩展性:适用于从小型局域网到大型复杂网络的各种场景。
OSPF协议的工作机制
链路状态广告(LSA)
OSPF使用链路状态广告(LSA)来传播链路状态信息。LSA包含直连网段、路由开销、邻居路由器等信息。路由器通过泛洪机制将LSA发送给其他路由器,确保全网透明。
邻居关系建立
在OSPF中,邻居关系的建立是协议运行的基础。通过Hello协议选举指定路由器(DR)和备份指定路由器(BDR),以减少链路状态信息的冗余传输。邻居关系建立后,路由器间互相交换LSA,形成链路状态数据库。
最短路径优先算法(SPF)
OSPF核心算法是Dijkstra的SPF算法。每个路由器使用链路状态数据库中的LSA信息,独立计算到各节点的最短路径,生成路由表。SPF算法确保路径无环路且总开销最小。
区域划分
OSPF支持多区域划分,通常分为骨干区域(Area 0)和非骨干区域(常规区域)。区域边界路由器负责汇总和传播跨区域LSA,减少区域内外通信量,优化网络性能。
数据库同步
一旦邻居关系建立,路由器开始交换链路状态信息,并构建自己的链路状态数据库(LSDB)。所有区域内的路由器最终会拥有相同的LSDB。
OSPF协议的优势与局限
优势
- 无环路:基于SPF算法,确保路径无环路。
- 快速收敛:局部调整链路状态,快速响应网络变化。
- 可扩展性:支持多区域划分,适应不同规模的网络。
- 灵活性:丰富的配置选项,支持多种复杂网络需求。
局限性
- 复杂配置:初学者难以理解,配置复杂。
- 资源占用高:需要较多计算和存储资源维护链路状态数据库。
- 不适应小型网络:对于简单网络,过度设计可能导致资源浪费。
术语定义
路由器/Router:
一种三层IP包的交换设备。在早期的IP文献中被称为网关/gateway。
自制系统/Autonomous System:
一组使用相同路由协议交换路由信息的路由器,缩写为AS。
内部网关协议/Interior Gateway Protocol:
被一个AS内的路由器所使用的路由协议,缩写为IGP。每个AS使用单一的IGP,不同的AS会使用不同的IGP。
路由器标识/Router ID:
一个32位的数字,用以识别每台运行OSPF协议的路由器。在一个AS中,这个数字可以唯一地表示出一台路由器。
网络/Network:
表示IP网络/子网/超网。一个物理网络上可能设置有多个网络/子网号,我们把它们按照独立的网络来对待。物理点对点/point-to-point网络是个例外–无论在上面
设置了多少网络/子网号(如果有的话),都将其看作是一个网络。
网络掩码/Network mask:
一个32位的数字,表示IP地址的范围来说明这是一个IP网络/子网/超网。本文以16进制来表示网络掩码。如将C类IP地址的网络掩码显示为0xffffff00,这一掩码在其他文献中经常被表示为255.255.255.0。
点对点网络/Point-to-point networks:
仅仅连接一对路由器的网络。56k的串行线路是一个点对点网络的例子。
广播网络/Broadcast networks:
支持多台(大于两台)路由器接入的网络,同时有能力发送一条信息就能到所有接入的路
由器(广播)。网络上邻居路由器可以通过OSPF的Hello协议来动态发现。如果可能,OSPF协议将进一步使用多播。广播网络上的每一对路由器都被认为可以直接通讯。以太网/ethernet是一个广播网络的例子。
非广播网络/Non-broadcast networks:
支持多台(大于两台)路由器接入的网络,但没有广播能力。网络上的邻居路由器通过OSPF的Hello协议来维持。但由于缺乏广播能力,需要一些配置信息的帮助来发现邻居。在非广播网络上,OSPF协议的数据通常需要被轮流发送到每一台邻居路由器上。X.25公用数据网/Public Data Network(PDN)是一个非广播网络的例子。
在非广播网络上运行的OSPF有两种模式。第一种被称为非广播多路接入/non-broadcast multi-access(NBMA),模拟OSPF在广播网络上的操作;第二种被称为点对多点/Point-to-Multipoint,将非广播网络看作是一系列点对点的连接。非广播网络被作为NBMA网络还是点对多点网络,取决于OSPF在该网络上所配置的运行模式。
接口/Interface:
是指路由器与所接入的网络之间的一个连接。接口通过下层协议和路由协议获取与其相关的状态信息。指向网络的接口只和单一的IP地址及掩码相关(除非是无编号的点对点网络)。接口有时也被称为连接/link。
邻居路由器/Neighboring routers:
在同一网络中都有接口的两台路由器。邻居关系是由OSPF的Hello协议来维持,并通常依靠Hello协议来动态发现。
邻接/Adjacency:
用以在所选择的邻居路由器之间交换路由信息的关系。不是每对邻居路由器都会成为邻接。
连接状态宣告/Link state advertisement:
描述路由器或网络自身状态的数据单元。对路由器来说,这包含它的接口和邻接状态。每一项连接状态宣告都被洪泛到整个路由域中。所有路由器和网络连接状态宣告的集合形成了协议的连接状态数据库。在本备忘录中,连接状态宣告被缩写为LSA。
Hello协议/Hello Protocol:
在OSPF协议中,用于建立和维持邻居关系的部分。在广播网络中还被用于动态发现邻居路由器。
洪泛/Flooding:
在OSPF协议中,用于OSPF路由器之间发送及同步连接状态数据库的部分。
指定路由器/Designated Router:
在每个接入了至少两台路由器的广播和NBMA网络中都有一台作为指定路由器。指定路由器生成Network-LSA并在运行协议时完成其他特定职责。指定路由器通过Hello协议选举。指定路由器的概念减少了广播和NBMA网络上所需要的邻接数量。同时也减少了路由协议所需要的流量及连接数据库的大小。
下层协议/Lower-level protocols:
为IP及OSPF协议提供服务的下层网络接入协议。如为X.25PDN服务的X.25packetandframelevel,以及为以太网服务的以太网数据链路层。
深入解析:OSPF协议的实现(C/C++代码实现)
/* OSPF packet header structure. */
typedef struct {
uint8_t version; /* OSPF Version. */
uint8_t type; /* Packet Type. */
uint16_t length; /* Packet Length. */
in_addr_t router_id; /* Router ID. */
in_addr_t area_id; /* Area ID. */
uint16_t checksum; /* Check Sum. */
uint16_t auth_type; /* Authentication Type. */
/* Authentication Data. */
union {
/* Simple Authentication. */
uint8_t auth_data[8];
/* Cryptographic Authentication. */
struct {
uint16_t zero; /* Should be 0. */
uint8_t key_id; /* Key ID. */
uint8_t auth_data_len; /* Auth Data Length. */
uint32_t crypt_seqnum; /* Cryptographic Sequence Number. */
};
};
} ospf_header;
/* OSPF Hello body format. */
typedef struct {
in_addr_t network_mask;
uint16_t hello_interval;
uint8_t options;
uint8_t priority;
uint32_t dead_interval;
in_addr_t d_router;
in_addr_t bd_router;
in_addr_t neighbors[];
} ospf_hello;
#define OSPF_ROUTER_LSA 1
#define OSPF_NETWORK_LSA 2
#define OSPF_SUMMARY_LSA 3
#define OSPF_ASBR_SUMMARY_LSA 4
#define OSPF_AS_EXTERNAL_LSA 5
/* OSPF LSA header. */
typedef struct {
uint16_t age;
uint8_t options;
uint8_t type;
in_addr_t id;
in_addr_t ad_router;
int32_t seq_num;
uint16_t checksum;
uint16_t length;
} lsa_header;
#define ROUTERLSA_ROUTER 1
#define ROUTERLSA_TRANSIT 2
#define ROUTERLSA_STUB 3
#define ROUTERLSA_VIRTUAL 4
/* OSPF Router-LSAs structure. */
typedef struct {
uint8_t flags;
uint8_t zero;
uint16_t num_link;
struct link {
in_addr_t id;
in_addr_t data;
uint8_t type;
uint8_t tos;
uint16_t metric;
} links[];
} router_lsa;
/* OSPF Network-LSAs structure. */
typedef struct {
in_addr_t mask;
in_addr_t routers[];
} network_lsa;
/* OSPF Summary-LSAs structure. */
typedef struct {
in_addr_t mask;
union {
uint8_t tos;
uint32_t metric;
};
} summary_lsa;
/* OSPF AS-external-LSAs structure. */
typedef struct {
in_addr_t mask;
struct {
union {
uint8_t tos;
uint32_t metric;
};
in_addr_t fwd_addr;
uint32_t route_tag;
} e[];
} as_external_lsa;
/* Flags in Database Description packet */
#define DD_FLAG_MS 0x01
#define DD_FLAG_M 0x02
#define DD_FLAG_I 0x04
/* OSPF Database Description body format. */
typedef struct {
uint16_t mtu;
uint8_t options;
uint8_t flags;
uint32_t seq_num;
lsa_header lsahs[];
} ospf_dd;
/* OSPF Link State Request */
typedef struct {
uint32_t ls_type; /* LS type */
uint32_t ls_id; /* Link State ID */
uint32_t ad_router; /* Advertising Router */
} ospf_lsr;
typedef enum {
S_Down,
S_Init,
S_2Way,
S_ExStart,
S_Exchange,
S_Loading,
S_Full
} nbr_state;
typedef enum {
E_HelloReceived,
E_1WayReceived,
E_2WayReceived,
E_AdjOK,
E_NegotiationDone,
E_ExchangeDone,
E_LoadingDone
} nbr_event;
typedef struct {
nbr_state src_state;
nbr_event event;
nbr_state dst_state;
} transition;
typedef struct neighbor {
int inact_timer; /* Inactivity Timer */
int rxmt_timer;
nbr_state state;
int master;
int more;
uint32_t dd_seq_num;
in_addr_t router_id; /* Router ID */
in_addr_t ip; /* IP */
uint8_t priority;
int num_lsah;
lsa_header lsahs[256];
int num_lsr;
ospf_lsr lsrs[256];
int num_ack;
lsa_header acks[256];
struct neighbor *next;
} neighbor;
typedef struct {
char name[16];
int state;
int sock;
struct area *a;
in_addr_t area_id;
in_addr_t ip;
in_addr_t mask;
in_addr_t dr;
in_addr_t bdr;
int hello_interval;
int dead_interval;
int hello_timer;
int wait_timer;
uint16_t cost;
uint16_t inf_trans_delay;
int rxmt_interval;
int num_nbr;
neighbor *nbrs;
} interface;
typedef struct {
in_addr_t ip;
in_addr_t mask;
in_addr_t next;
uint16_t metric;
const char *iface;
} route;
typedef struct area {
int num_if;
interface *ifs[16];
int num_lsa;
lsa_header *lsas[256];
int num_route;
route routes[256];
} area;
...
void dijkstra(int root) {
...
for (;;) {
int p = -1, q;
/* find out the nearest unused node */
for (int j = 0; j < num_node; ++j)
if (!use[j] && (p == -1 || nodes[p].dist > nodes[j].dist))
p = j;
if (p == -1 || nodes[p].dist == DISTANCE_INF) break;
use[p] = 1;
/* update distance of the rest nodes */
if (nodes[p].lsa->type == OSPF_ROUTER_LSA) {
router_lsa *rlsa = (router_lsa *)((uint8_t *)nodes[p].lsa +
sizeof(lsa_header));
int j = ntohs(rlsa->num_link);
nodes[p].mask = 0;
for (const struct link *l = rlsa->links; j--; ++l) {
int k;
switch (l->type) {
case ROUTERLSA_ROUTER:
case ROUTERLSA_TRANSIT:
k = find_node(l->id);
break;
case ROUTERLSA_STUB:
printf("leaf: %s\n", inet_ntoa((struct in_addr){l->id}));
leaf->id = l->id;
leaf->mask = l->data;
leaf->next = nodes[p].next;
leaf->dist = nodes[p].dist + ntohs(l->metric);
leaf->lsa = nodes[p].lsa;
pre[leaf - nodes] = p;
++leaf;
default:
continue;
}
if (k == num_node || use[k]) continue;
int det = ntohs(l->metric);
if (nodes[k].dist > nodes[p].dist + det) {
nodes[k].dist = nodes[p].dist + det;
pre[k] = p;
}
}
} else if (nodes[p].lsa->type == OSPF_NETWORK_LSA) {
network_lsa *nlsa = (network_lsa *)((uint8_t *)nodes[p].lsa +
sizeof(lsa_header));
int j = (ntohs(nodes[p].lsa->length) - sizeof(lsa_header) -
sizeof(nlsa->mask)) /sizeof(in_addr_t);
nodes[p].mask = nlsa->mask;
for (const in_addr_t *rt = nlsa->routers; j--; ++rt) {
int k = find_node(*rt);
if (k == num_node || use[k]) continue;
router_lsa *rlsa = (router_lsa *)((uint8_t *)nodes[k].lsa +
sizeof(lsa_header));
int num = ntohs(rlsa->num_link);
while (num--) {
if (rlsa->links[num].type == ROUTERLSA_TRANSIT &&
rlsa->links[num].id == nodes[p].id) break;
if (rlsa->links[num].type == ROUTERLSA_STUB &&
rlsa->links[num].id == (nodes[p].id & nlsa->mask)) break;
}
if (num < 0) continue;
int det = ntohs(rlsa->links[num].metric);
if (nodes[k].dist > nodes[p].dist + det) {
nodes[k].dist = nodes[p].dist + det;
pre[k] = p;
}
}
}
/* calculate the next router id */
for (q = p; q != root; q = pre[q])
if (!nodes[q].mask) nodes[p].next = nodes[q].id;
}
num_node = leaf - nodes;
}
void spt(lsa_header *const lsas[], int num_lsa) {
int root;
num_node = 0;
/* lsa type 1 and lsa type 2 */
for (int i = 0 ; i < num_lsa; ++i) {
if (lsas[i]->type == OSPF_ROUTER_LSA || lsas[i]->type == OSPF_NETWORK_LSA) {
nodes[num_node].id = lsas[i]->id;
nodes[num_node].lsa = lsas[i];
nodes[num_node].dist = DISTANCE_INF;
if (nodes[num_node].id == myid) root = num_node;
++num_node;
}
}
dijkstra(root);
/* lsa type 3 ans lsa type 4 */
for (int i = 0 ; i < num_lsa; ++i) {
if (lsas[i]->type == OSPF_SUMMARY_LSA ||
lsas[i]->type == OSPF_ASBR_SUMMARY_LSA) {
summary_lsa *slsa = (summary_lsa *)((uint8_t *)lsas[i] +
sizeof(lsa_header));
if (find_node(lsas[i]->id) < num_node) continue;
int k = find_node(lsas[i]->ad_router);
if (k == num_node || nodes[k].dist == DISTANCE_INF) continue;
nodes[num_node].id = lsas[i]->id;
nodes[num_node].mask = slsa->mask;
nodes[num_node].next = nodes[k].next;
nodes[num_node].dist = nodes[k].dist + ntohl(slsa->metric >> 4 << 4);
nodes[num_node].lsa = lsas[i];
++num_node;
}
}
/* lsa type 5 */
//TODO
}
void process_lsu(area *a, neighbor *nbr, ospf_header *ospfhdr);
void produce_lsu(const area *a, const neighbor *nbr, ospf_header *ospfhdr);
void produce_upd(const area *a, const lsa_header *lsa, ospf_header *ospfhdr);
void process_lsr(neighbor *nbr, ospf_header *ospfhdr);
void produce_lsr(const neighbor *nbr, ospf_header *ospfhdr);
uint16_t fletcher16(const uint8_t *data, size_t len);
lsa_header *gen_router_lsa(area *a);
void add_lsah(neighbor *nbr, const lsa_header *lsah);
int lsah_eql(const lsa_header *a, const lsa_header *b);
/* return value < 0: b is newer, > 0: a is newer , = 0: the same */
int cmp_lsah(const lsa_header *a, const lsa_header *b);
lsa_header *find_lsa(const area *a, const lsa_header *lsah);
lsa_header *insert_lsa(area *a, const lsa_header *lsah);
void process_ack(neighbor *nbr, ospf_header *ospfhdr);
void produce_ack(const neighbor *nbr, ospf_header *ospfhdr);
void process_dd(interface *iface, neighbor *nbr, const ospf_header *ospfhdr);
void produce_dd(const interface *iface, const neighbor *nbr, ospf_header *ospfhdr);
...
If you need the complete source code, please add the WeChat number (c17865354792)
运行效果:
总结
OSPF协议具有快速收敛、无环路设计和区域划分等优点,适用于各种规模的网络环境。然而,OSPF也存在一些局限性,如复杂配置和资源消耗等。未来研究可以进一步探索如何优化OSPF的配置和管理,以提高其在大规模网络中的性能和可靠性。
Welcome to follow WeChat official account【程序猿编码】
文献综述
已有大量文献对OSPF协议进行了深入研究和分析。李勇和沈秀娟在《OSPF协议原理分析及其两种仿真实验设计与实现》中详细探讨了OSPF协议的基本原理和仿真实验设计。蒋磊在《论OSPF协议的基本原理与实现》一文中阐述了OSPF协议的核心算法和实现方法。钮鑫在《OSPF路由协议原理及特点》中介绍了OSPF协议的主要特性和应用优势。此外,许多学者也对OSPF协议在不同网络环境中的实际应用进行了研究,如程永青的《浅谈OSPF协议的基本原理及其仿真》和刘洪宾的《路由协议OSPF仿真与探究》。