2.4 网络设备的编程接口

前面我们讨论了开始NetDevOps之前的一些知识点,这些内容基本上还没有涉及具体的网络设备这一侧。既然是NetDevOps,我们必然需要和网络设备打交道。网络设备需要什么能力才能更加适合NetDevOps?我们如何看待与评估?这是本节将要阐述的内容。

2.4.1 网络设备接口的分类

网络设备的核心任务是转发数据包,网络工程师的核心任务是控制网络设备按照预定的设计转发数据包。网络工程师在控制网络设备的时候有以下三类方式:

❑ 传统的接口;

❑ 路由协议层面;

❑ 数据包层面。

(1)传统的接口类型

对于网络工程师而言,最传统的方式是通过命令行的方式修改设备的配置。修改设备配置的接口有多种,最常见的是用Telnet、SSH或者直接用Console口登录到设备上进行操作。除此之外,许多厂家的设备可以通过SNMP的write(写)能力对设备进行配置修改。对于支持NETCONF的设备,也可以通过NETCONF接口发送XML或JSON格式的内容对设备进行配置修改,从而达到改变设备转发路径的目的。同理,部分厂家使用NETCONF的方式也可以归结到这一类接口。这一类接口主要用于处理非结构化或结构化的文本信息,在后续的章节中,我们会详细讨论如何处理非结构化与结构化的文本信息。

(2)通过路由协议或类路由协议的类型

这类方式发送广义的NLRI(Network Layer Reachability Information)给设备,设备在收到这些信息后将其转化为硬件能识别的转发表项下推送到硬件上,从而完成网络设备转发路径的变更。这一类中,较为常见的还有PCEP(Path Computation Element Communication Protocol)、BGP、BGP-LU等协议方式。另外,著名的OpenFlow协议也属于这一类。OpenFlow发送给网络设备的流表(flow table)并不能算真正意义上的转发表,它和BGP-FlowSpec在很大程度上还是比较相似的。不过,OpenFlow拥有更多的字段,能完成更多的事情。例如,OpenFlow定义的字段存在一些纯控制平面使用的内容,Cookie就是一个用于控制的标示。这一类接口主要处理的是这些广义NLRI信息的包结构,以及转发路径和这些包的对应关系。这部分的内容比第一类的接口类型相对复杂一些,本书主要关注的是NetDevOps入门的内容,因此不会涉及这一部分的开发。读者如有兴趣可以参考ExaBGP、RYU等开源项目。

(3)数据包层面的类型

第三类和前两类不一样,前两类可以归纳为:通过改变设备的转发表而影响设备的转发路径。转发表是可以通过静态配置或者路由协议计算来生成的。而第三类并不修改大部分网络设备的转发表信息,而是在数据包中添加更多的包头信息,从而实现转发路径的更改。这类方式最为典型的是Segment Routing和NSH(Network Services Headers)。Segment Routing通过MPLS标签堆栈来增加数据包中的转发信息,网络设备通过读取数据包头中的这些信息获取相应的转发路径。而相比Segment Routing, NSH(参考https://tools.ietf.org/html/draft-ietf-sfc-nsh-12)携带了更加灵活的信息。由于它比MPLS Label更加地灵活,目前用ASIC来实现NSH的识别和转发有一定的难度。但是,NSH非常适合在软件方式的网络设备(如vRouter、vSwitch等NFV设备)上来实现和完成。这里举两个例子来区分一下第三类和前两类的区别。

【例1】 运送包裹,见图2-4。包裹在经过每一个中转站的时候是完全无法控制的,也不知道下一个中转站的情况。每一个中转站通过查询包裹的目的地址,从而知道如何往哪里投递(转发)它。这个过程和现在的IP网络非常类似,它和IP网一个比较大的区别是物流网络往往有较大的缓存,这些缓存就是物流的中转站。即使中转站爆仓,包裹被丢弃的概率还是非常低的。

图2-4 包裹运送例子

图2-5 汽车导航

【例2】 汽车导航,见图2-5。这个例子和例1看似有点类似,但是还是有本质的区别。在这个例子中,车行驶的路径在出发前就已经规划好了(假设在出发后,司机并不改变行驶路径)。在这个规划中,每个路口都有一个提示。司机在到达这个路口时会根据这个提示进行操作,完成后这个提示就会被丢弃掉,司机需要关注的只是下一个提示。这个过程和目前的IP转发是完全不一样的。Segment Routing和NSH的方式就与汽车导航的方式非常相似。以Segment Routing为例,数据包在进入MPLS SR域的边缘节点时,数据包头会被加上了一串MPLS的标签。这些标签就像图2-5中每一个路口的提示信息。

本书不会涉及这部分的应用开发,目前此类的应用相对还比较少,这类的开发也相对复杂,已经超出了NetDevOps入门的阶段。但在本书的第14章会有一个关于路径计算的案例。在此案例中,当完成路径计算后,会通过BGP协议给网络设备发送路由信息,达到控制路径转发的目的。

2.4.2 网络设备编程接口的特征

2.4.1节中提到了网络设备接口分类的问题,那么网络编程对设备的接口有什么要求呢?这里归纳了编程接口的四个基本特征:

❑ 结构化数据;

❑ 无连接与无状态性;

❑ 事务性;

❑ 幂等性。

1.结构化数据

目前网络设备输出的数据主要是面向人的显示方式(大多为CLI输出结果),这样格式的数据缺少一些结构化的标识。对于人来说,数据的显示和信息的分类主要是通过换行和空格符进行标识的,但这样的显示方式对于程序而言并不是很方便。程序更加容易处理使用封闭符号(各种括号与引号等)的信息嵌套格式,程序对空格与换行完全没有任何要求。对于使用换行符和空格符格式化的数据,本书暂且称它们为半结构化数据(这里指的半结构化是相对JSON/XML而言的)。对于结构化数据以及半结构化数据的处理方法,在本书的Python部分会提供详细的介绍。

2.无连接与无状态性

无连接指的是每次连接后只处理一个请求。服务端处理完客户端的请求,并收到客户的确认后,立即断开连接。无状态指的是对于事务的处理没有记忆性,服务端也不知道客户端是什么状态,服务端只是根据请求发送数据。现在网络设备的CLI接口几乎都不具备这样的能力,CLI方式是强交互式的方式。举例来说,如果我们需要在一台Cisco传统路由器上配置一个接口的IP地址,首先需要登录到设备上,然后输入命令enable进行入enable模式,接着输入命令config terminal进入配置模式,再输入interface xxx进入接口配置模式,到这里才可以进行接口IP地址的配置。对设备的这几个请求必须是按顺序的,并且每次请求后都不能和设备断开连接,一旦断开了连接,必须重新开始。

NETCONF或者RESTful的接口基本上实现了无连接与无状态性,如果网络设备支持的话,对于开发而言将会更加方便。

对于不具备无连接与无状态性的网络设备的开发,本书后面的章节会有讨论。对于具备无连接与无状态性的网络设备,本书的后续章节也将会有涉及。

3.事务性

事务性是指在执行一个操作时,要么成功执行完成,要么根本不执行。假定现在对一台交换机有如下一个事务需要操作:在交换机的上行接口中添加一个VLAN。对于网络工程师而言,这个事务通常可以拆解为两个小事件。首先在交换机上添加一个VLAN ID(假定原来不存在这个VLAN),然后在上行接口中增加这个VLAN ID。如果这两个小事件不能组成一个事务,就有可能出现如下这样的问题:提交的上行接口名称错误,导致在上行接口上增加VLAN ID这个事件失败;但是第一个在交换机上创建VLAN这个事件先执行完成,然后又没有事务性的回滚机制,最终导致在设备上留下了这样一个多余的VLAN ID。此类的操作或许是网络设备上出现很多“垃圾”配置的原因之一。时间久了,大量的、无效的“垃圾”配置将给运维工作带来非常大的痛苦。NETCONF(https://tools.ietf.org/html/rfc6241)中定义的candidate配置、commit以及Rollback-on-Error的功能,将能很好地保证设备具备事务性的处理能力。

4.幂等性(idempotence)

这个概念来自于数学,现在计算机科学也借用了这个概念,其含义是指重复使用同样的参数调用同一方法时总能获得同样的结果。举例来说,下面permit ip any 2.2.2.2/32这行命令就不是一个幂等的方法。设备在这个命令前自动添加了一个序列号20。当t1这个access-list被其他人修改后,再运行一次permit ip any 2.2.2.2/32就会得到完全不一样的效果。

        switch(config-acl)# show ip access-list t1
        IP access list t1
            10 permit ip any 1.1.1.1/32
        switch(config-acl)# permit ip any 2.2.2.2/32
        switch(config-acl)# show ip access-list t1
        IP access list t1
            10 permit ip any 1.1.1.1/32
            20 permit ip any 2.2.2.2/32

这里修改了t1这个access-list后,重新运行了一次permit ip any 2.2.2.2/32,就得到了完全不一样的结果。这条命令如果修改为20 permit ip any 2.2.2.2/32,就会解决这个问题,不过要避免序列号20这个在acl中没有被使用,那么每次运行这条命令就可以得到完全一样的结果。

        switch(config-acl)# show ip access-list t1
        IP access list t1
            10 permit ip any 1.1.1.1/32
            30 permit ip any 3.3.3.3/32
        switch(config-acl)# permit ip any 2.2.2.2/32
        switch(config-acl)# show ip access-list t1
        IP access list t1
            10 permit ip any 1.1.1.1/32
            30 permit ip any 3.3.3.3/32
            40 permit ip any 2.2.2.2/32

这个例子就是幂等性的问题,这在编程时需要注意。

对于上述四个特性,如果网络设备的API都能直接支持将是最好的。但是在很多情况下,往往不尽如人意,很多时候我们不得不通过传统的CLI方式与网络设备进行交互。当然,即使没有上述的特性,也不代表我们不能通过程序来实现一些功能,只不过我们在编写程序的时候需要处理更多的内容。