Floodlight 入门 之 起步篇 - 如何处理PacketIN消息
2017-3-2
小论文写不出来好纠结,今天还在做Floodlight开发,好烦躁。这边博文介绍如何处理PacketIN消息,是从官网翻译的,原味教程大家有兴趣的话就嚼一嚼。
介绍
在OpenFlow-SDN网络中,当一个交换机在一个端口上接受到一个packet时,它会将这个packet与交换机默认的流表中的流表项进行匹配。如果交换机不能定位到一个能匹配该packet的流表项,默认情况下它将会发送一个packet给控制器(这个packet就是PACKET_IN),交由控制器处理(原文是 it will by default* send the packet to the controller as a packet-in for closer inspection and processing,不太理解“for closer inspection and processing”的意思,所以这里暂且没有翻译。)(OpenFLow1.0,1.1和1.2中流表展示了默认情况下的行为,而在1.3及其以上版本中,需要由控制器将缺省的流插入到交换机中,这里Floodlight会为你做这些工作。)
在Floodlight 入门 之 起步篇 - 建立一个Floodlight模块这一篇中,我们已经知道如何在一个自定义的模块中接受一个packet-in消息了。该教程会教你如何解释你接收到的packet的header和payload。
检索来自控制器内核的以太网帧
任何一个监听PacketIn消息的模块都很有可能试图去检查Payload。与其每个独立的模块都对PacketIn消息的字节数组形式的Payload进行一次并行化的操作,还不如让Floodlight内核来做,这样每个消息只要进行一次并行化的操作就可以了。所以,在我们自定义的模块中,我们不需要对PacketIn消息的字节数组形式的Payload进行并行化的操作,只需要使用与该packet关联的FloodlightContext来获得已经并行化的Ethernet对象就可以了。
/*
* Overridden IOFMessageListener's receive() function.
*/
@Override
public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
switch (msg.getType()) {
case PACKET_IN:
/* Retrieve the deserialized packet in message */
Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
/* We will fill in the rest here shortly */
break;
default:
break;
}
return Command.CONTINUE;
}
如上所示,我们使用FloodlightContext获取以太网帧。在后台(Under the hood),Floodlight通过并列化OpenFlow的PacketIn消息的Payload来创建Ethernet(对象)。
当我们获得Ethernet对象后,我们就能立马使用Ethernet对象的getters和setters了。这能让我们获得源MAC地址,或者VLAN ID之类的信息。
/*
* Overridden IOFMessageListener's receive() function.
*/
@Override
public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
switch (msg.getType()) {
case PACKET_IN:
/* Retrieve the deserialized packet in message */
Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
/* Various getters and setters are exposed in Ethernet */
MacAddress srcMac = eth.getSourceMACAddress();
VlanVid vlanId = VlanVid.ofVlan(eth.getVlanID());
/* Still more to come... */
break;
default:
break;
}
return Command.CONTINUE;
}
获取Ethernet的Payload(例如IPv4或者ARP)
注意我们现在仅仅能够访问以太网帧的header,并不能访问更高层的header。要获取更高层的header的访问权限的话,我们要先确定以太网帧的Payload。然后,我们就能获得代表该Payload的并行化的packet(原文 After we do this, we can retrieve the deserialized packet representing the payload.)。这通常是一个IPv4或者ARP的packet。
/*
* Overridden IOFMessageListener's receive() function.
*/
@Override
public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
switch (msg.getType()) {
case PACKET_IN:
/* Retrieve the deserialized packet in message */
Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
/* Various getters and setters are exposed in Ethernet */
MacAddress srcMac = eth.getSourceMACAddress();
VlanVid vlanId = VlanVid.ofVlan(eth.getVlanID());
/*
* Check the ethertype of the Ethernet frame and retrieve the appropriate payload.
* Note the shallow equality check. EthType caches and reuses instances for valid types.
*/
if (eth.getEtherType() == EthType.IPv4) {
/* We got an IPv4 packet; get the payload from Ethernet */
IPv4 ipv4 = (IPv4) eth.getPayload();
/* More to come here */
} else if (eth.getEtherType() == EthType.ARP) {
/* We got an ARP packet; get the payload from Ethernet */
ARP arp = (ARP) eth.getPayload();
/* More to come here */
} else {
/* Unhandled ethertype */
}
break;
default:
break;
}
return Command.CONTINUE;
}
如上所示,一旦我们确定了Payload的类型,我们就可以将Payload强制转换成正确的class。所有在net.floodlightcontroller.packet中被定义的packet都会继承BasePacket(他实现了IPacket)。因为Floodlight内核已经对packet进行并行化了,我们所需要做的就是讲packet强制转换成它对应的对象类型上。
注意,我们确定了packet的类型并获得了它的Payload,那我们就可以访问packet的header attributes了。
/*
* Overridden IOFMessageListener's receive() function.
*/
@Override
public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
switch (msg.getType()) {
case PACKET_IN:
/* Retrieve the deserialized packet in message */
Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
/* Various getters and setters are exposed in Ethernet */
MacAddress srcMac = eth.getSourceMACAddress();
VlanVid vlanId = VlanVid.ofVlan(eth.getVlanID());
/*
* Check the ethertype of the Ethernet frame and retrieve the appropriate payload.
* Note the shallow equality check. EthType caches and reuses instances for valid types.
*/
if (eth.getEtherType() == EthType.IPv4) {
/* We got an IPv4 packet; get the payload from Ethernet */
IPv4 ipv4 = (IPv4) eth.getPayload();
/* Various getters and setters are exposed in IPv4 */
byte[] ipOptions = ipv4.getOptions();
IPv4Address dstIp = ipv4.getDestinationAddress();
/* Still more to come... */
} else if (eth.getEtherType() == EthType.ARP) {
/* We got an ARP packet; get the payload from Ethernet */
ARP arp = (ARP) eth.getPayload();
/* Various getters and setters are exposed in ARP */
boolean gratuitous = arp.isGratuitous();
} else {
/* Unhandled ethertype */
}
break;
default:
break;
}
return Command.CONTINUE;
}
获取IPv4的Payload(例如TCP或者UDP)
逻辑上,我们能够在深入到IPv4的packet,获得TCP或者UDP的Payload。
/*
* Overridden IOFMessageListener's receive() function.
*/
@Override
public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
switch (msg.getType()) {
case PACKET_IN:
/* Retrieve the deserialized packet in message */
Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
/* Various getters and setters are exposed in Ethernet */
MacAddress srcMac = eth.getSourceMACAddress();
VlanVid vlanId = VlanVid.ofVlan(eth.getVlanID());
/*
* Check the ethertype of the Ethernet frame and retrieve the appropriate payload.
* Note the shallow equality check. EthType caches and reuses instances for valid types.
*/
if (eth.getEtherType() == EthType.IPv4) {
/* We got an IPv4 packet; get the payload from Ethernet */
IPv4 ipv4 = (IPv4) eth.getPayload();
/* Various getters and setters are exposed in IPv4 */
byte[] ipOptions = ipv4.getOptions();
IPv4Address dstIp = ipv4.getDestinationAddress();
/*
* Check the IP protocol version of the IPv4 packet's payload.
*/
if (ipv4.getProtocol() == IpProtocol.TCP) {
/* We got a TCP packet; get the payload from IPv4 */
TCP tcp = (TCP) ipv4.getPayload();
/* Various getters and setters are exposed in TCP */
TransportPort srcPort = tcp.getSourcePort();
TransportPort dstPort = tcp.getDestinationPort();
short flags = tcp.getFlags();
/* Your logic here! */
} else if (ipv4.getProtocol() == IpProtocol.UDP) {
/* We got a UDP packet; get the payload from IPv4 */
UDP udp = (UDP) ipv4.getPayload();
/* Various getters and setters are exposed in UDP */
TransportPort srcPort = udp.getSourcePort();
TransportPort dstPort = udp.getDestinationPort();
/* Your logic here! */
}
} else if (eth.getEtherType() == EthType.ARP) {
/* We got an ARP packet; get the payload from Ethernet */
ARP arp = (ARP) eth.getPayload();
/* Various getters and setters are exposed in ARP */
boolean gratuitous = arp.isGratuitous();
} else {
/* Unhandled ethertype */
}
break;
default:
break;
}
return Command.CONTINUE;
}
上述就是这个教程的全部。这个流程同样适用于获取其他类型的Payload, IPv4, ARP, TCP,还有UDP仅仅是教程中的例子。
如果你的packet或者frame的Payload并没有被定义成net.floodlightcontroller.packet中的一个类,你也可以选择性地把它加进去或者用返回的Data代替。所有未知的Payload都会被表示成Data IPackets,它有getData方法来返回字节数组形式的Payload。
Note that deserialization is done one header/payload at a time. This means that if you have an Ethernet frame with an IPv4 payload but an unknown-to-Floodlight transport protocol, the packet will be deserialized up to the transport layer. When Floodlight examine’s the IPv4 IpProtocol and does not find a pre-defined transport packet, the deserialization process will stop and getPayload() of IPv4 will return a Data object instead. The prior successfully deserialized headers and payloads remain intact and are accessible to modules in their deserialized form.(这段翻译的不好,有问题的话还望指正)
注意,一次只能对一个header或Payload进行并行化操作。这意味着如果你有一个以太网帧(携带者一个IPv4的Payload而不是一个Floodlight未知的传输层协议),这个packet会并列化直到传输层。当Floodlight分析到IPv4 IP协议时,并且并没有发现预定义的传输层packet,并行化操作将会停止并且IPv4的getPayload()将会返回一个Data对象。原先成功并列化的header和Payload将会原封不动地保留下来并且可访问。