51学通信论坛2017新版

标题: OpenFlow协议库开发者指南 [打印本页]

作者: admin    时间: 2017-9-17 14:50
标题: OpenFlow协议库开发者指南
编者按:本文系SDNLAB社区译者计划发布文章,SDNLAB将与国外优质媒体和个人进行长期的内容合作,带来更多的优质技术文章。
[attach]1349[/attach]


本文译者:toles,虽然是个SDN初学者,但是很看好SDN前景,在不断努力的学习中,希望和大家多多交流。

</p>介绍

OpenFlow协议库是OpenDaylight的一个组件,调解OpenDaylight controller和支持OpenFlow协议的硬件设备之间通信。主要目标是提供用户(或上层OpenDaylight)通信通道,可用于管理网络硬件设备。

功能概览

Openflowjava内部的三个特性:


odl-openflowjava-protocol架构

此特性包含基本的bundles有openflow-protocol-api, openflow-protocol-impl, openflow-protocol-spi and 工具.


odl-openflowjava-stats 特性

运行在odl-openflowjava-protocol上.它包含了各种的信息类型/事件和报告计数在特定时间周期内. 统计信息收集被配置在openflowjava- config/src/main/resources/45-openflowjava-stats.xml

关键API和接口

基础API/SPI类是ConnectionAdapter (Rpc/通知) 和SwitchConnectionProcider (配置, 启动, 关闭)

安装

检出代码并导入工程到你的IDE. git clone ssh://@git.opendaylight.org:29418/openflowjava.git

配置

当前实现允许配置:

你可以在如下所示找到典型的Openflow 协议库实例配置:
Shell
<data xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<modules xmlns="urn:opendaylight:params:xml:ns:yang:controller:config">
<!-- default OF-switch-connection-provider (port 6633) -->
<module>
<type xmlns:prefix= "urn:opendaylight:params:xml:ns:yang:openflow:switch:connection:provider:impl">prefix:openfl switch-connection-provider-impl</type>
<name>openflow-switch-connection-provider-default-impl</name>
<port>6633</port>
<!-- Possible transport-protocol options: TCP, TLS, UDP -->
<transport-protocol>TCP</transport-protocol>
Shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

<switch-idle-timeout>15000</switch-idle-timeout>
<!-- Exemplary TLS configuration:
-uncomment the <tls> tag
-copy exemplary-switch-privkey.pem, exemplary-switch-cert.pem and exemplary-cacert.pem
files into your virtual machine
-set VM encryption options to use copied keys
-start communication
Please visit OpenflowPlugin or Openflow Protocol Library#Documentation wiki pages
for detailed information regarding TLS -->
<!-- <tls>
<keystore>/exemplary-ctlKeystore</keystore>
<keystore-type>JKS</keystore-type>
<keystore-path-type>CLASSPATH</keystore-path-type>
<keystore-password>opendaylight</keystore-password>
<truststore>/exemplary-ctlTrustStore</truststore>
<truststore-type>JKS</truststore-type>
<truststore-path-type>CLASSPATH</truststore-path-type>
<truststore-password>opendaylight</truststore-password>
<certificate-password>opendaylight</certificate-password>
</tls> -->
<!-- Exemplary thread model configuration. Uncomment <threads> tag below to adjust default thread model -->
<!-- <threads>
<boss-threads>2</boss-threads>
<worker-threads>8</worker-threads>
</threads> -->
</module>

Shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

<!-- default OF-switch-connection-provider (port 6653) -->
<module>
<type xmlns:prefix= "urn:opendaylight:params:xml:ns:yang:openflow:switch:connection:provider:impl">prefix:openfl switch-connection-provider-impl</type>
<name>openflow-switch-connection-provider-legacy-impl</name>
<port>6653</port>
<!-- Possible transport-protocol options: TCP, TLS, UDP -->
<transport-protocol>TCP</transport-protocol>
<switch-idle-timeout>15000</switch-idle-timeout>
<!-- Exemplary TLS configuration:
-uncomment the <tls> tag
-copy exemplary-switch-privkey.pem, exemplary-switch-cert.pem and exemplary-cacert.pem
files into your virtual machine
-set VM encryption options to use copied keys
-start communication
Please visit OpenflowPlugin or Openflow Protocol Library#Documentation wiki pages
for detailed information regarding TLS -->
<!-- <tls>
<keystore>/exemplary-ctlKeystore</keystore>
<keystore-type>JKS</keystore-type>
<keystore-path-type>CLASSPATH</keystore-path-type>
<keystore-password>opendaylight</keystore-password>
<truststore>/exemplary-ctlTrustStore</truststore>
<truststore-type>JKS</truststore-type>
<truststore-path-type>CLASSPATH</truststore-path-type>
<truststore-password>opendaylight</truststore-password>
<certificate-password>opendaylight</certificate-password>

<type xmlns:prefix= "urn:opendaylight:params:xml:ns:yang:openflow:common:config:impl">prefix:openflow- provider-impl</type>
<name>openflow-provider-impl</name>
<openflow-switch-connection-provider>
<type xmlns:ofSwitch= "urn:opendaylight:params:xml:ns:yang:openflow:switch:connection:provider">ofSwitch:openflow- switch-connection-provider</type>
<name>openflow-switch-connection-provider-default</name>
</openflow-switch-connection-provider>
<name>openflow-switch-connection-provider-legacy</name>
</openflow-switch-connection-provider>
<binding-aware-broker>
<type xmlns:binding= "urn:opendaylight:params:xml:ns:yang:controller:md:sal:binding">binding:binding- broker-osgi-registry</type>
<name>binding-osgi-broker</name>
</binding-aware-broker>
</module>
</modules>
可能传输协议选项:
交换机空闲超时指定时间检测交换机的空闲状态.当一段时间内没有收到来自交换机的消息,上层被通知交换机闲置.可以使用典型的TLS配置:

线程模型配置指定需要多少个线程以执行 Netty 的 I/O 操作.


架构

公共API(openflow-protocol-api)

接口和构建者集合代表Openflow协议结构的不可变数据传输对象.
为了减少冗长的定义和重复性代码,通过代码生成器从YANG模型推出传输对象和服务API.
以下是YANG模型的定义:

这个模块也可以从下面的YANG模型中重复使用类型:

预定义类型的使用使API约定更安全, 有更好的可读性和记录(例如 用 MacAddress代替字节数组…)

TCP 通道pipeline(openflow-protocol-impl)

创建基于配置和支撑的通道处理pipeline.
TCP通道流水线.imageopenflowjava/500px-TCPChannelPipeline.png[width=500]
交换连接提供者.用于其它工程连接点的实现.库通过本类公开其功能.库能够在这里被配置, 启动和关闭.也有方法为客户定制的 (反) 序列化注册.
Tcp连接初始化程序.为了初始化TCP连接到一个设备(交换机),OF插件调用在SwitchConnectionProvider的方法initiateConnection。该方法依次初始化(Bootstrap)通向设备的服务侧通道。
TCP处理(TCP Handler).表示单服务通过TCP/TLS协议处理进入的连接。TCP处理程序创建一个单实例的TCP通道初始化程序对通道进行初始化。之后监听配置过的InetAddress和端口。当一个新设备连接,TCP处理程序注册他们的通道并把控制传给TCP通道初始化程序。
TCP通道初始化程序.此类用于通道初始化/拒绝和传递参数.之后一个新通道被注册,它调用交换连接处理(OF Plugin)接收方法决定是否库应该保持新的注册通道或者是否通道应该被关闭. 如果通道已经被接受, TCP通道初始化程序创建整个管道所需的处理与ConnectionAdapter 实例.之后通道pipeline准备好了,交换连接处理程序被onConnectionReady通知. OpenFlow 插件可以开始发送下游信息.
空闲处理程序.如果超过指定时间没有收到任何消息,这个处理程序触发空闲状态通知.交换机从ConnectionConfiguration设置收到空闲超时参数.当交换空闲超时内收到消息空闲状态处理程序处于非激活状态.如果超过超时值没有收到指定信息,处理程序创建SwitchIdleEvent消息并发送给下游.
TLS处理程序.通过TLS协议加密和解密消息.约束TLS
处理器进入pipeline 是配置的事情( tag). TLS通信要么不支持要么是必需支持. TLS处理程序作为Netty SslHandler表示.
OF帧解码器. 解析输入流进入正确长度的消息帧为further处理.帧基于Openflow头长度. 如果收到的消息比OpenFlow最短消息(8字节)短, OF帧解码器等待更多的数据.接收至少为8字节后,解码器检查OpenFlow头长度.如果仍然有一些字节丢失,解码器等待它们.其他的帧解码器发送正确长度的消息到下一个处理程序中的通道管道.
OF版本探测器.检测使用OpenFlow协议的版本并丢弃不受支持的版本消息.如果检测版本支持, OF版本探测器创建包含检测的版本和字节消息的VersionMessageWrapper目标,并且向上游发送对象.
OF解码器.选择正确的对象反序列化工厂 (基于消息类型) 并且反序列化消息生成DTO (数据传输对象). OF解码器接收VersionMessageWrapper对象并将其传递到DeserializationFactory返回转换的DTO. DeserializationFactory创建带版本和接收消息类型的MessageCodeKey对象和对象类被接收消息序列化. 在DecoderTable搜索相应解码器时此对象被用作秘钥. DecoderTable实际上是一个map存储解码器.找到解码器翻译成接收消息进入DTO.如果没有找到解码器, 返回null.之后返回转换的DTO回到OF解码器,解码器检查是否为null.当DTO为null时,解码器记录日志并且抛出异常.否则传递DTO further到上游.最后, OF解码器释放ByteBuf包含的接收和解码字节消息.
OF编码器.选择正确的序列化工厂 (基于DTO类型) 并且序列化DTOs为字节消息. OF编码器相对于解码器使用同样的原则. OF Encoder receives DTO OF编码器接收DTO,如果结果不为null把它转化,它发送转化的DTO作为 ByteBuf到下游.通过MessageTypeKey寻找合适的编码器,基于版本和接收的DTO类.
授权入栈处理程序.授权收到的DTO到连接适配器.在channelInactive和channelUnregistered事件中反应.其中一个事件触发, DelegatingInboundHandler 创建DisconnectEvent消息并且发到上游,通知上层交换断链.
通道出站队列.消息刷新处理程序.
存储输出消息(DTOs)并刷新.刷新的执行是基于时间过期和消息队列数.
连接适配器.提供了pipeline顶部的外观,隐藏了netty.io特性.提供了一种方法来注册传入的消息并将消息发送到特定的通道/会话. ConnectionAdapterImpl主要实现了3个接口(统一在一个超接口ConnectionFacade中):

ConnectionAdapter接口有用于设置监听器的方法(消息, 系统和连接准备监听器),该方法检查是否所有监听器被设置,检查是否通道存活并断开连接方法. 断链方法清除responseCache并禁用新消息.
MessageConsumer接口只有一个方法: consume. Consume方法被DelegatingInboundHandler方法调用.此方法基于其类型处理接收的DTO.有三种接收对象类型:

OpenflowProtocolService接口包含了全部rpc-methods为发送消息从上层到下游并响应。请求消息返回将来填充的期望回复消息,否则这个期望的将来是Void类型。
注意: MultipartRequest消息是唯一例外.实际上它是请求-应答消息类型, 如果是作为rpc实现它不能处理更多的MultipartReply 消息(only one Future).这是为什么MultipartReply作为通知的实现. OpenFlow插件负责纠正消息处理.

UDP通道pipeline (openflow-protocol-impl)

创建以配置和支撑为基础的UDP通道处理pipeline.交换机连接提供者, 通道出站队列和连接适配器与TCP连接/通道pipeline的情况下实现的作用相同 (请看上面).

[attach]1350[/attach]


Figure 16.1. UDP Channel pipeline

UDP处理程序.代表一个单独的服务器正在处理UDP (DTLS)协议之上的传入连接. UDP处理程序创建一个UDP通道初始化的单例实例,这个实例将出示通道.之后监听绑定配置的地址和端口.当一个新设备连接, UDP处理程序注册通道并传递控制权给UDP通道初始化程序.
UDP通道初始化程序.这个类被用于通道初始化和传递参数.之后一个新通道被注册(UDP也永远只有一个通道) UDP通道初始化程序创建整个流水线与所需要的处理程序.
DTLS处理程序.还没有实现.将处理安全DTLS连接.
OF数据报文处理程序.结合OF帧解码器和OF版本检测器功能.从接收数据报文提取消息并检查消息版本是否支持.如果收到的消息来自未知发送机, OF报文处理程序为此发送机创建连接适配器并将其存储在UdpConnectionMap发送机的地址.此map也被用于发送消息和正确连接适配器查找,委托消息从一个通道到多个会话.
OF数据报文解码器.选择正确的反序列化工厂 (基于消息类型)并且反序列化消息进入生成DTOs.OF解码器接收VersionMessageUdpWrapper对象将其传递到DeserializationFactory,返回转化的DTO. DeserializationFactory创建带版本和接收消息类型的MessageCodeKey对象并将接收到的消息反序列化为对象的类.此对象被用作在DecoderTable搜索相应解码器的关键字. DecoderTable是基于映射存储解码器. 发现解码器转换接收的消息进入DTO (DataTransferObject).如果没有发现解码器, 返回null.之后返回转换的DTO到OF报文解码器,此解码器检查是否为null.当DTO为null,解码器把此状态记录日志中.否则在UdpConnectionMap内通过DTO找到相应的连接适配器.最后, OF解码器释放包含的ByteBuf和解码字节消息.
OF数据报文编码器.选择正确的序列化工厂(基于DTO的类型)并且串行DTOs成为字节消息. OF报文编码器使用了相同原则做相反的处理. OF编码器接收DTO并转换,如果结果不为null,转换为数据包发送给下游,通过MessageTypeKey寻找相应的编码器, 基于版本和接收DTO的类.

SPI (openflow-protocol-spi)

定义接口用于其他工程库的连接点. 库通过这个接口来公开其功能.

集成测试(openflow-protocol-it)

测试与简单的客户端通信.

简单客户端(simple-client)

轻量级交换机仿真器 –期望场景可编程.

实用工具 (util)

包含实用工具类,主要用于和ByteBuf一起工作.

库的生命周期

步骤 (之后库的bundle启动):

[attach]1351[/attach]


图 16.2. 库的生命周期

统计信息收集器

介绍

统计信息采集器采集消息统计. Current collected statistics (DS - downstream, US - upstream):


Karaf

为了开启统计, 需要特性:install odl-openflowjava-stats. To see the logs one should use log:set DEBUG org.opendaylight.openflowjava.statistics and than probably log:display (you can log:list to see if the logging has been set).调整集合设置修改modify 45-openflowjava-stats.xml足以.

JConsole

Jconsole为统计信息收集器提供两个命令:
之后JConsole 附属到正确的处理程序上, 只需进入MBeans tab # org.opendaylight.controller # RuntimeBean # statistics-collection- service-impl # statistics-collection-service-impl # Operations 能使用这些命令.

TLS Support

注意

请看OpenFlow Plugin Developper Guide

可扩展性

介绍

扩展性入口点是SwitchConnectionProvider. SwitchConnectionProvider 为(解)序列化注册包含了方法c.注册解序列化需要使用.register*Deserializer(key, impl). 注册序列化必须使用.register*Serializer(key, impl).注册可以发生在配置过程中或者运行时.
注意:假设当接收到实验者信息,没有(反)序列化器被注册,此库将抛出IllegalArgumentException.

基本原理

为了使用扩展需要增加现有模型和注册新(反)序列化器.
增加模型: 1. 创建新增加
Register (de)serializers: 1.创建你的(反)序列化器 2. 实现OFDeserializer<> / OFSerializer<> -以防你(反)序列化接口需要使用多TableFeatures消息, 让它实现HeaderDeserializer<> / HeaderSerializer 3.实现规定的方法 4.在相应的秘钥下注册你的反序列化器 (案例 ExperimenterActionDeserializerKey) 5.相应的秘钥下注册你的序列化器 (in our case ExperimenterActionSerializerKey) 6. 完成, 测试你的实现
注意:如果你不知道用什么秘钥实现你的(反)序列化,请看Registration keys一页.

例子

假设我们有供应商/实验者动作,由这种结构表示:
Shell
struct foo_action { uint16_t type; uint16_t length; uint32_t experimenter; uint16_t first; uint16_t second; uint8_t pad[4];
}
首先, 我们必须增强现有的模型. 我们创建一个新模型, 导入"openflow-types.yang" (不要忘记更新你的pom.xml和api依赖).现在我们创建了foo操作标识:
Shell
import openflow-types {prefix oft;} identity foo {
description "Foo action description"; base oft:action-base;
}
这将作为我们结构中的类型. 现在我们必须增强现有的action结构,以致我们将有所需的第一和第二字段.为了创建新扩展, 我们的模块不得不导入"openflow-action.yang". 增加如下:
Shell
import openflow-action {prefix ofaction;}
augment "/ofaction:actions-container/ofaction:action" { ext:augment-identifier "foo-action";
Shell
leaf first {
type uint16;
}
leaf second {
我们完成了模型的改变. 运行mvn clean编译生成源代码.生成后,我们需要实现我们的(反)序列化.
反序列化:
Shell
public class FooActionDeserializer extends OFDeserializer<Action> {
@Override
public Action deserialize(ByteBuf input) { ActionBuilder builder = new ActionBuilder;
input.skipBytes(SIZE_OF_SHORT_IN_BYTES); *// we know the type of action*
builder.setType(Foo.class); input.skipBytes(SIZE_OF_SHORT_IN_BYTES); *// we don't need length*
*// now create experimenterIdAugmentation - so that openflowplugin can differentiate correct vendor codec*
ExperimenterIdActionBuilder expIdBuilder = new ExperimenterIdActionBuilder;
expIdBuilder.setExperimenter(new ExperimenterId(input. readUnsignedInt));
builder.addAugmentation(ExperimenterIdAction.class, expIdBuilder. build);
FooActionBuilder fooBuilder = new FooActionBuilder; fooBuilder.setFirst(input.readUnsignedShort); fooBuilder.setSecond(input.readUnsignedShort); builder.addAugmentation(FooAction.class, fooBuilder.build); input.skipBytes(4); *// padding*
return builder.build;
}
序列化:
Shell
public class FooActionSerializer extends OFSerializer<Action> {
@Override
public void serialize(Action action, ByteBuf outBuffer) { outBuffer.writeShort(FOO_CODE); outBuffer.writeShort(16);
*// we don't have to check for ExperimenterIdAction augmentation - our serializer*
*// was called based on the vendor / experimenter ID, so we simply
write
it to buffer*
outBuffer.writeInt(VENDOR / EXPERIMENTER ID);
FooAction foo = action.getAugmentation(FooAction.class); outBuffer.writeShort(foo.getFirst); outBuffer.writeShort(foo.getSecond); outBuffer.writeZero(4); //write padding
序列化和反序列化注册:
Shell
SwitchConnectionProvider.registerDeserializer(new
ExperimenterActionDeserializerKey(0x04, VENDOR / EXPERIMENTER ID), new FooActionDeserializer); SwitchConnectionProvider.registerSerializer(new
ExperimenterActionSerializerKey(0x04, VENDOR / EXPERIMENTER ID), new FooActionSerializer);
我们已经准备好测试我们的实现.
NOTE:供应商/实验者结构只定义供应商/实验者ID标识(除操作类型).对全部供应商消息,供应商/实验者ID是唯一的-这就是为什么供应商能只注册在一个ExperimenterAction(De)SerializerKey类下.这就是为什么供应商不得不在他们拥有的子类/子类型交换/选择.

详细演练:反序列化可扩展性

外部接口& 类描述. OFGeneralDeserializer:
DeserializerRegistryInjector

NOTE: DeserializerRegistryInjector 不是OFGeneralDeserializer派生的.它是一个独立的接口.
MessageCodeKey及他们的后代被用作在DeserializerRegistry反序列化查找. MessageCodeKey 应该在一般情况下使用,然而它的派生类用在更特殊的情况下.例如ActionDeserializerKey被用作行动解序列化器查找和(解)注册.供应商提供仅包含最必要字段特殊关键字. 这些关键字通常开始带Experimenter前缀(MatchEntryDeserializerKey 是一个异常).
MessageCodeKey有这些域:

场景介绍

当返序列化程序需要访问其他反序列化程序特别有用. 举例 IntructionsDeserializer 需要访问 ActionsDeserializer 为了能够处理OFPIT_WRITE_ACTIONS/OFPIT_APPLY_ACTIONS指令.

[attach]1352[/attach]


Detailed walkthrough: 序列化可扩展性

外部接口& 类描述.OFGeneralSerializer:

SerializerRegistryInjector* injectSerializerRegistry(SerializerRegistry) – 注入serializer registry into serializer.当序列化程序需要访问其他序列化程序时有用.
NOTE:SerializerRegistryInjector不是OFGeneralSerializer后代.
MessageTypeKey和它们的后代这些关键字被用于序列化器在SerializerRegistry内查找. MessageTypeKey当它的后代被用在特殊的情况使用.例如ActionSerializerKey被用于Action反序列化器查找注销. 供应商提供仅包含最必要字段特殊关键字.这些关键字通常开始带Experimenter前缀 (MatchEntrySerializerKey是一个异常).
MessageTypeKey 存在这些域:

class方案演练

当序列化器需要访问其他反序列化器时特别有用.例如IntructionsSerializer为了能够处理OFPIT_WRITE_ACTIONS/OFPIT_APPLY_ACTIONS指令需要访问ActionsSerializer.

[attach]1353[/attach]


Figure 16.4. Serialization scenario walkthrough

内部描述

SwitchConnectionProvider SwitchConnectionProvider构造和初始化序列化器和反序列化器注册默认的(反)序列化器. 拒绝DeserializerRegistry 进入DeserializationFactory, SerializerRegistry 进入 SerializationFactory.当调用自定义的(反)序列化, SwitchConnectionProvider在适当的注册表调用注册方法.
DeserializerRegistry / SerializerRegistry为了初始化默认的(反)序列化器DeserializerRegistry / SerializerRegistry,注册表都包含init方法.注册表检查是否关键字或(反)序列化器实现不为null.如果至少有一个是null, 抛出NullPointerException.否则如果他是(De)SerializerRegistryInjector实例,(反)序列化器被检查.如果它是这个接口的实例,注册表被注入进(反)序列化实现.
GetSerializer(key) 或 GetDeserializer(key)执行注册查找. 因为有两个独立接口可能放入注册表,此注册表用作它们的统一super接口. 获得(De)Serializer(key) 方法 强制转换super接口为所需的类型.从注册表接收有一个null检查为(反)序列化器.如果反序列化器没有找到, NullPointerException 与秘钥描述被抛出.

注册秘钥

反序列化. openflow扩展和秘钥
这有三个供应商特性的扩展在Openflow v1.0和八个在Openflow v1.3.这些扩展被注册在注册密钥下:

[attach]1354[/attach]


Table 16.1. Deserialization反序列化

序列化. openflow扩展和秘钥
在Openflow v1.0有三个供应商特定扩展Openflow v1.3有七个.这些扩展被注册在秘钥下,如下表所示:

[attach]1355[/attach]


Table 16.2. 序列化

SDNLAB社区译者 正在火热招募中


成为译者的好处:

什么样的人才能成为译者?
怎样成为译者?
1、添加微信:353176266 或点击识别二维码
2、进行自我介绍
3、阅读社区提供的翻译资料
4、翻译测试

声明:本文转载自网络。版权归原作者所有,如有侵权请联系删除。




欢迎光临 51学通信论坛2017新版 (http://bbs.51xuetongxin.com/) Powered by Discuz! X3