摘要
基于OpenFlow的SDN网络中,控制器可以通过OpenFlow的消息”OFPT_PACKET_OUT”,直接发送任意报文到网络的数据通路中。本文介绍了在ODL环境下如何直接通过控制器发送任意报文的两种方法。第一种使用控制器的RESTCONF接口packet-processing(2013-07-09),采用HTTP协议和JSON格式直接发送任意的报文到网络中;第二种方法编写内嵌于ODL的APP应用,采用PacketProcessingService服务接口发送任意报文。两种方法在ODL的版本Boron(硼)和Beryllium(铍)均测试通过。
1 介绍
OpenDaylight作为一款开源SDN网络控制器,依托于强大的社区支持以及功能特性,成为了目前主流的SDN网络控制器开发平台。不仅为开发者提供了大量的网络管理功能,而且藉由AD-SAL(API驱动的服务层)和MD-SAL(模型驱动的服务层), 给独立的网络应用提供了完善的二次开发接口。尤其是其提供的REST API接口, 基于HTTP协议及JSON/XML格式进行接口的调用和数据的获取,独立于具体的开发语言,其使用非常的方便。
1.1 YANG模型和NETCONF协议
YANG模型是一种数据建模语言,用来建模由NETCONF协议定义的配置数据和状态数据、远端过程调用(RPCs)、和NETCONF通知(notification), 具有良好的可读性和可扩展性。设备端和客户端都可以使用YANG进行数据的建模, 设备侧提供了YANG数据模型后,客户端可依据工具自动生成对应的访问模型代码,大大的节省开发工作量。其具体的定义可以参看RFC 6020: “YANG - A Data Modeling Language for the Network Configuration Protocol (NETCONF)” 。
NETCONF(Network Configuration Protocol,网络配置协议)是一种基于XML的网络管理协议,它提供了一种可编程的、对网络设备进行配置和管理的方法。用户可以通过该协议设置参数、获取参数值、获取统计信息等。NETCONF报文使用XML格式,具有强大的过滤能力,而且每一个数据项都有一个固定的元素名称和位置,这使得同一厂商的不同设备具有相同的访问方式和结果呈现方式,不同厂商之间的设备也可以经过映射XML得到相同的效果,这使得它在第三方软件的开发上非常便利,很容易开发出在混合不同厂商、不同设备的环境下的通用的管理软件。在管理软件的协助下,使用NETCONF功能会使网络设备的配置管理工作,变得更简单更高效。
NETCONF协议采用了分层结构,分成四层:内容层、操作层、RPC(Remote Procedure Call,远程调用)层和通信协议层。
其具体定义可看RFC 6241:” Network Configuration Protocol (NETCONF)”。
最初的网络管理协议SNMP也有对应的建模语言SMI。而操作ODL的应用采用的RPC接口、HTTP协议和XML格式数据,就构成了SOAP协议内容。
在ODL中,通过YANG模型来建模应用的配置数据和状态数据,以及RPC和Notification.
NETCONF/SNMP/YANG/SOAD/REST其对比关系如下图:
图 11 SNMP/NETCONF/SOAD/REST对比
1.2 报文发送接口packet-processing
在ODL中,安装了bundle” odl-restconf”和” odl-netconf-connector-all”后,打开浏览器的地址:
http://:8181/ apidoc/explorer/index.html (以ODL版本Boron-SR1为例),就能看到相应的REST 公开的接口,其中RPC调用接口” packet-processing”提供了发送任意报文的功能, 此功能由openflowplugin提供, 如下图:
该接口的yang文件定义在ODL的bundle“openflowplugin”里面:openflowplugin/model/model-flow-service/src/main/yang/packet-processing.yang
此接口的yang RPC定义内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
rpc transmit-packet { description "Sending packet out through openflow device."; input { uses inv:node-context-ref; leaf connection-cookie { type connection-cookie; } leaf egress { type inv:node-connector-ref; } leaf buffer-id { type uint32; } uses raw-packet; uses action-type:action-list; } } |
使用此接口时,需要构造:
- raw-packet: 整个原始报文。报文内容包含数据链路层。Yang的定义为binary类型,采用HTTP协议发送时,此二进制内容以Base64编码为ASCII字符后发送
- egress: 报文出口,这里指的是网络中交换机的端口,此端口格式为ODL中的node-connector-ref
- node: 发送此报文的交换机,为ODL中的node-ref
- action-list: 可选功能。如果提供action, 那么action-list的最后一个动作是output-action来发送报文,否则报文不能从交换机发送出去
Url: 发送的url为 http://:8181/restconf/operations/packet-processing:transmit-packet
1.3 PacketProcessingService服务
此服务由openflowplugin的模块model-flow-service提供。只有一个接口transmitPacket。定义如下:
1 2 3 4 5 |
/** * Sending packet out through openflow device. * */ Future<RpcResult<java.lang.Void>> transmitPacket(TransmitPacketInput input); |
2 实现
两种方法的实现,下面具体操作。
2.1 网络拓扑
网络拓扑如下图所示。希望通过ODL控制器从Dev-PC上发送自定义的原始报文到Test-PC上。
2.2 REST接口之packet-processing
根据yang定义文件构造相应的请求报文即可。其中的payload原始数据为二进制数据,需要采用Base64编码为ASCII字符串序列。
构造JSON请求报文的java代码如下(采用Nayuki’s library for JSON):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public String buildInput_transmit_packet(){ byte[] rawPacket=getRawByte(xxx); String nodeid="openflow:11"; String egress="openflow:11:2"; Map<String,Object> input = new TreeMap<String,Object>(); input.put("connection-cookie", 123456); String strNode = "/opendaylight-inventory:nodes/opendaylight-inventory:node[opendaylight-inventory:id='"+nodeid+"']"; input.put("node",strNode); input.put("egress",strNode + "/opendaylight-inventory:node-connector[opendaylight-inventory:id='"+ egress+"']"); //payload input.put("payload", Base64Util.encode(rawPacket)); Map<String,Object> packet = new TreeMap<String,Object>(); packet.put("input", (Object)input); return Json.serialize(packet); } |
构造HTTP请求,调用REST接口发送到控制器上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public void doTransmitPacket() throws Exception{ String strUrl = "http://<IP>:8181/restconf/operations/packet-processing:transmit-packet"; HttpUtil http = new HttpUtil(strUrl); http.addBasicAuth("admin","admin"); http.addHeaderField("Accept","application/json"); http.addHeaderField("Content-type","application/json"); String strEntity = buildInput_transmit_packet(); System.out.println(strEntity); http.addEntity(strEntity); http.sendRequest(HttpUtil.Method.POST); } |
2.3 PacketProcessingService服务接口
此种方法需要开发一个ODL的app。下面以Boron-SR1版本依次说明操作步骤。
1) 生成项目骨架
命令行下面输入如下指令:(注意指定版本为1.2.1-Boron-SR1)
1 2 3 4 5 |
mvn archetype:generate -DarchetypeGroupId=org.opendaylight.controller \ -DarchetypeArtifactId=opendaylight-startup-archetype\ -DarchetypeVersion=1.2.1-Boron-SR1 \ -DarchetypeRepository=https://nexus.opendaylight.org/content/repositories/public/\ -DarchetypeCatalog=https://nexus.opendaylight.org/content/repositories/public/archetype-catalog.xml |
输入mypacket作为项目名称。
2) 定义提供接口的yang文件(可选)
在api子模块下面的yang文件中定义如下的rpc接口send-packet,用于接收外部提供的参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
rpc send-packet{ input{ leaf rawpacket{ type binary; } leaf egress { type inv:node-connector-ref; } leaf node { type inv:node-ref; } } output{ leaf result{ type string; } } } |
3) 子模块impl实现发送报文功能
发送报文功能函数packetOut:
1 2 3 4 5 6 |
private void packetOut(NodeRef egressNodeRef, NodeConnectorRef egressNodeConnectorRef, byte[] payload) { // Construct input for RPC call to packet processing service TransmitPacketInput input = new TransmitPacketInputBuilder().setPayload(payload).setNode(egressNodeRef) .setEgress(egressNodeConnectorRef).build(); packetProcessingService.transmitPacket(input); } |
实现send-packet的RPC接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Override public Future<RpcResult<SendPacketOutput>> sendPacket(SendPacketInput input) { SendPacketOutputBuilder packetBuilder = new SendPacketOutputBuilder(); NodeConnectorRef egress = input.getEgress(); NodeRef node = input.getNode(); byte[] payload = input.getRawpacket(); packetOut(node,egress, payload); //send packet!! packetBuilder.setResult("OK"); return RpcResultBuilder.success(packetBuilder.build()).buildFuture(); } |
4) 添加相应的依赖, 并编译运行
在api/pom.xml文件中添加依赖:model-inventory
在impl/pom.xml文件中添加依赖:openflowplugin-model的model-flow-service依赖
在feature/pom.xml添加上述2个依赖,
在feature/ src/main/features/features.xml文件中分别添加如下的内容:
1 2 3 |
<repository>mvn:org.opendaylight.openflowplugin/features-openflowplugin/${openflowplugin.version}/xml/features</repository> …… <feature version='${openflowplugin.version}'>odl-openflowplugin-southbound</feature> |
最后运行如下脚本启动并运行app:
1 2 |
$ mvn clean install $ ./karaf/target/assembly/bin/karaf |
5) 运行测试
浏览器打开"http://:8181/apidoc/explorer/index.html"
找到” mypacket(2015-01-05)”,其中mypacket为第一步定义的项目名。
构造如下的JSON输入串,然后点击”try it out!”. 可以在Test-PC上运行wireshark看到我们发送的自定义报文。
1 |
{"input": {"egress": "/opendaylight-inventory:nodes/opendaylight-inventory:node[opendaylight-inventory:id='openflow:10']/opendaylight-inventory:node-connector[opendaylight-inventory:id='openflow:10:2']", "node": "/opendaylight-inventory:nodes/opendaylight-inventory:node[opendaylight-inventory:id='openflow:10']", "rawpacket": "QAwpyJZqAAwp06cxCABFAAA8fR0AAEABaCjAqAoVwKgKFggATJcCAP7EYWJjZGVmZ2hpamtsbW5vcHFyc3R1dndhYmNkZWZnaGk="}} |
3 结论
发送报文是网络设备的最基本功能。本文我们介绍了ODL环境中的两种直接发送任意原始报文的方法。并且在ODL的版本Boron(硼)和Beryllium(铍)都测试通过。需要注意的是:通过HTTP构造请求时,二进制类型的原始报文需要编码为Base64类型,ODL系统会自动进行解码。
本文完整的代码:https://github.com/siwind/mypacket
作者简介:王寅庆, 目前在北京邮电大学网络技术研究院学习。 曾经供职于行业内某通信技术公司, 做过软件研发, 带领过项目团队。 关注于网络、SDN、大数据和信息安全等方面研究。