编者按:这次主要是关于一篇OpenDaylight module开发的指南,主要是能够收到交换机发来的Packet-in消息,同时能够将收到的packet中的一些消息写入到一个文件中,并向交换机下发流表。
一、结构描述
画一下结构图,我们需要的功能应该加在哪里得明确了。从图中看出我们加入的内容应该在SAL层上面,和SwitchManger等一系列Manger在一层上。
下面确定依赖关系和需要实现的接口。
二、确定依赖关系
我们需要开发的是紫色的那个模块,如下面UML图所示,要实现IListenDataPacket接口,同时我们需要依赖于SAL层的两个服务:FlowProgrammerService和DataPacketService,这两个服务聚合于SAL层;蓝色的是SAL层几个比较重要的接口。
画出UML类图,模块的基本关系我们也清楚了,下一步就是设计方案了。
由于功能比较简单,就是收到Packet-in消息后进行一系列处理。
下面确定这个module放在哪里,我们都知道在controller/opendaylight/samples内放置了ODL开发者提供的几个例子,我写的也放在这里。
三、pom.xml文件编写
在samples内,建立一个文件:
1 2 |
mkdir mytest cd mytest |
现在开始写pom.xml文件,这个之前文章里有大神写过pom.xml详细介绍的,我就不班门弄斧了,直接开始写pom.xml。
pom.xml内容如下:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.opendaylight.controller.samples.mytest</groupId> <artifactId>mytest</artifactId> <version>0.5.6-SNAPSHOT</version> <packaging>bundle</packaging> <name>mytest</name> <url>http://maven.apache.org</url> <build> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <extensions>true</extensions> <configuration> <instructions> <Export-Package>org.opendaylight.controller.samples.mytest </Export-Package> <Import-Package> * </Import-Package> <Bundle-Activator>org.opendaylight.controller.samples.mytest.Activator</Bundle-Activator> <Service-Component></Service-Component> </instructions> <manifestLocation>${project.basedir}/META-INF</manifestLocation> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> </dependency> <dependency> <groupId>org.opendaylight.controller</groupId> <artifactId>sal</artifactId> <version>0.7.0</version> </dependency> <dependency> <groupId>org.opendaylight.controller</groupId> <artifactId>switchmanager</artifactId> <version>0.7.0</version> </dependency> </dependencies> <repositories> <!--OpenDaylight releases --> <repository> <id>opendaylight-mirror</id> <name>opendaylight-mirror</name> <url>http://nexus.opendaylight.org/content/groups/public/</url> <snapshots> <enabled>false</enabled> </snapshots> <releases> <enabled>true</enabled> <updatePolicy>never</updatePolicy> </releases> </repository> <!--OpenDaylight snapshots --> <repository> <id>opendaylight-snapshot</id> <name>opendaylight-snapshot</name> <url>http://nexus.opendaylight.org/content/repositories/opendaylight.snapshot/</url> <snapshots> <enabled>true</enabled> </snapshots> <releases> <enabled>false</enabled> </releases> </repository> </repositories> </project> |
对于<build>内的内容,主要是为了定义输出包和需要依赖的包(输入包);<dependencies>内定义依赖包;至于在<dependencies>内看到switchmanager,是我以后开发工作还需要继续做别的内容,所以就先写上了,目前我们要实现的功能里不会用到。
四、代码编写
写好pom.xml文件,下面开始写代码,
1 |
vim src/main/java/org/opendaylight/controller/samples/mytest/PacketHandler.java |
完整PacketHandler.java代码如下:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
packageorg.opendaylight.controller.samples.mytest; importjava.net.InetAddress; importjava.net.UnknownHostException; importjava.util.LinkedList; importjava.util.List; importorg.opendaylight.controller.sal.core.Node; importorg.opendaylight.controller.sal.core.NodeConnector; importorg.opendaylight.controller.sal.packet.Ethernet; importorg.opendaylight.controller.sal.packet.IDataPacketService; importorg.opendaylight.controller.sal.packet.IListenDataPacket; import org.opendaylight.controller.sal.packet.IPv4; importorg.opendaylight.controller.sal.packet.Packet; importorg.opendaylight.controller.sal.packet.PacketResult; importorg.opendaylight.controller.sal.packet.RawPacket; importorg.opendaylight.controller.sal.utils.Status; importorg.opendaylight.controller.switchmanager.ISwitchManager; importorg.opendaylight.controller.sal.flowprogrammer.Flow; import org.opendaylight.controller.sal.flowprogrammer.IFlowProgrammerService; importorg.opendaylight.controller.sal.match.Match; importorg.opendaylight.controller.sal.match.MatchType; importorg.opendaylight.controller.sal.action.Action; importorg.opendaylight.controller.sal.action.Output; importorg.opendaylight.controller.sal.action.SetDlDst; importorg.opendaylight.controller.sal.action.SetDlSrc; importorg.opendaylight.controller.sal.action.SetNwDst; importorg.opendaylight.controller.sal.action.SetNwSrc; import org.slf4j.Logger; import org.slf4j.LoggerFactory; importjava.io.FileOutputStream; importjava.io.File; public class PacketHandler implements IListenDataPacket { private static final Logger log = LoggerFactory.getLogger(PacketHandler.class); private static final byte[] SET_MAC = {0,0,0,0,0,0x08}; privateIDataPacketServicedataPacketService; privateIFlowProgrammerServiceflowProgrammerService; privateISwitchManagerswitchManager; static private InetAddressintToInetAddress(inti) { byte b[] = new byte[] { (byte) ((i>>24)&0xff), (byte) ((i>>16)&0xff), (byte) ((i>>8)&0xff), (byte) (i&0xff) }; InetAddressaddr; try { addr = InetAddress.getByAddress(b); } catch (UnknownHostException e) { return null; } returnaddr; } /* * Sets a reference to the requested DataPacketService * See Activator.configureInstance(...): * c.add(createContainerServiceDependency(containerName).setService( * IDataPacketService.class).setCallbacks( * "setDataPacketService", "unsetDataPacketService") * .setRequired(true)); */ voidsetDataPacketService(IDataPacketService s) { log.trace("Set DataPacketService."); dataPacketService = s; } /* * Unsets DataPacketService * See Activator.configureInstance(...): * c.add(createContainerServiceDependency(containerName).setService( * IDataPacketService.class).setCallbacks( * "setDataPacketService", "unsetDataPacketService") * .setRequired(true)); */ voidunsetDataPacketService(IDataPacketService s) { log.trace("Removed DataPacketService."); if (dataPacketService == s) { dataPacketService = null; } } /* * Sets a reference to the requested FlowProgrammerService */ voidsetFlowProgrammerService(IFlowProgrammerService s) { log.info("Set FlowProgrammerService."); System.out.println("Set FlowProgrammerService."); flowProgrammerService = s; } /* * Unsets FlowProgrammerService */ voidunsetFlowProgrammerService(IFlowProgrammerService s) { log.info("Removed FlowProgrammerService."); System.out.println("Removed FlowProgrammerService."); if (flowProgrammerService == s) { flowProgrammerService = null; } } /* * Sets a reference to the requested SwitchManagerService */ voidsetSwitchManagerService(ISwitchManager s) { log.info("Set SwitchManagerService."); System.out.println("Set SwitchManagerService."); switchManager = s; } /* * Unsets SwitchManagerService */ voidunsetSwitchManagerService(ISwitchManager s) { log.info("Removed SwitchManagerService."); System.out.println("Removed SwitchManagerService."); if (switchManager == s) { switchManager = null; } } @Override publicPacketResultreceiveDataPacket(RawPacketinPkt) { log.trace("Received data packet."); //这个connector是数据包来自的交换机 NodeConnectoringressConnector = inPkt.getIncomingNodeConnector(); Node node = ingressConnector.getNode(); // 利用DataPacketService解析数据包. Packet l2pkt = dataPacketService.decodeDataPacket(inPkt); if (l2pkt instanceof Ethernet) { Object l3Pkt = l2pkt.getPayload(); if (l3Pkt instanceof IPv4) { IPv4 ipv4Pkt = (IPv4) l3Pkt; InetAddressdestaddr = intToInetAddress(ipv4Pkt.getDestinationAddress()); InetAddresssrcaddr = intToInetAddress(ipv4Pkt.getSourceAddress()); try{ FileOutputStream out = new FileOutputStream(new File("/home/openflow/text.txt")); String s = "Packet to " + destaddr.toString() + " from "+ srcaddr.toString() +" received by node " + node.getNodeIDString() + " on connector " + ingressConnector.getNodeConnectorIDString(); out.write(s.getBytes()); out.close(); } catch (Exception e) { e.printStackTrace(); } Match match = new Match(); match.setField(MatchType.DL_TYPE, (short) 0x0800); // IPv4 ethertype match.setField(MatchType.NW_SRC, srcaddr); match.setField(MatchType.NW_DST, destaddr); //设计对packet使用的actions List<Action> actions = new LinkedList<Action>(); //设置新的目的IP actions.add(new SetNwDst(destaddr)); //设置新的目的MAC actions.add(new SetDlDst(SET_MAC)); //创建流 Flow flow = new Flow(match, actions); Status status = flowProgrammerService.addFlow(node, flow); if (!status.isSuccess()) { log.error("Could not program flow: " + status.getDescription()); } if(!status.isSuccess()) System.out.println("this flow seting failed! "); } returnPacketResult.KEEP_PROCESSING; } returnPacketResult.IGNORED; } } |
这部分代码里,我们确定实现IListenDataPacket,SET_MAT是下发流表里的一个action项,dataPacketService、flowProgrammerService、switchManager是后续需要的服务,函数intToInetAddress定义如何将一个int转换为IP地址,后续会用到。
上面截图的函数是核心函数,收到一个RawPacket类型的报文后,可以获取交换机的逻辑描述node,即开发人员只需要知道这个逻辑node,不用了解具体的实现,这也是ODL开发里利用面向对象编程的依赖倒转原则(实现依赖于接口),知道接口怎么用即可。这里调用dataPacketService来解析包,获取二层的MAC包l2pkt。
这部分里首先判断是否是Ethernet类型的数据包(MAC帧),利用Object l3Pkt = l2pkt.getPayload();获取IP层的报文l3pkt,如果是IPv4的报文,就会获取出报文的源IP地址和目的IP地址。然后打开一个文件,将一些数据写入到文件内。
上面的代码里创建Match域,写入匹配值,如源IP和目的IP ,然后设置Actions,对数据包进行修改目的IP和目的MAC(修改目的MAC会导致主机都收不到消息)。然后创建一条流flow,将match和actions加入进去,最后利用flowProgrammerService将流表下发下去,node就是前文讲的交换机逻辑节点。
最后是Activator.java的编写
1 |
vim src/main/java/org/opendaylight/controller/samples/mytest/Activator.java |
完整Activator.java代码如下:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
packageorg.opendaylight.controller.samples.mytest; importjava.util.Dictionary; importjava.util.Hashtable; importorg.apache.felix.dm.Component; import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase; import org.opendaylight.controller.sal.flowprogrammer.IFlowProgrammerService; importorg.opendaylight.controller.sal.packet.IDataPacketService; importorg.opendaylight.controller.sal.packet.IListenDataPacket; importorg.opendaylight.controller.switchmanager.ISwitchManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Activator extends ComponentActivatorAbstractBase { private static final Logger log = LoggerFactory.getLogger(PacketHandler.class); public Object[] getImplementations() { log.trace("Getting Implementations"); Object[] res = { PacketHandler.class }; return res; } public void configureInstance(Component c, Object imp, String containerName) { log.trace("Configuring instance"); if (imp.equals(PacketHandler.class)) { // Define exported and used services for PacketHandler component. Dictionary<String, Object> props = new Hashtable<String, Object>(); props.put("salListenerName", "mypackethandler"); // Export IListenDataPacket interface to receive packet-in events. c.setInterface(new String[] {IListenDataPacket.class.getName()}, props); // Need the DataPacketService for encoding, decoding, sending data packets c.add(createContainerServiceDependency(containerName).setService( IDataPacketService.class).setCallbacks( "setDataPacketService", "unsetDataPacketService") .setRequired(true)); // Need FlowProgrammerService for programming flows c.add(createContainerServiceDependency(containerName).setService( IFlowProgrammerService.class).setCallbacks( "setFlowProgrammerService", "unsetFlowProgrammerService") .setRequired(true)); // Need SwitchManager service for enumerating ports of switch c.add(createContainerServiceDependency(containerName).setService( ISwitchManager.class).setCallbacks( "setSwitchManagerService", "unsetSwitchManagerService") .setRequired(true)); } } } |
这部分主要是配置,将需要的服务设定好,比较简单。
五、编译安装
写完以后,在mytest下运行:
1 |
sudo mvn clean install -DskipTests |
成功构建后会是下面图片,同时target目录里会有生成的jar包:
下面用命令启动控制器:
1 |
sudo ./run.sh |
然后将数据包安装进去:
1 |
osgi> install file:你的application路径/mytest/target/mytest-0.5.6-SNAPSHOT.jar |
安装完会有如下图示:
从上图知道我们安装的bundle的id是344,所以执行如下命令:
1 |
osgi> start 344 |
然后我们关闭两个也接收数据包的服务,他们也实现了IListenDataPacket。
1 2 |
ss | grep simple ss | greploadbalance |
stop上处命令获取的bundle ID。
六、测试
最后启动mininet,其中192.168.3.101是控制器的IP
1 2 |
sudo mn --controller=remote,ip=192.168.3.101 mininet>pingall |
查看一下流表:
1 |
sudo ovs-ofctl dump-flows s1 |
根据前面代码,说明流表已经下发到交换机上,同时也在一个文件中写入了数据,内容如下显示:
说明文件内也写入了我们要求的数据。
以上功能全部实现!
作者简介:
王钰琪,2013/07-至今 北京邮电大学网络技术研究院 网络与交换技术国家重点实验室攻读硕士研究生