| /** |
| * Copyright 2011, Big Switch Networks, Inc. |
| * Originally created by David Erickson, Stanford University |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may |
| * not use this file except in compliance with the License. You may obtain |
| * a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| **/ |
| |
| package net.floodlightcontroller.forwarding; |
| |
| import static org.easymock.EasyMock.*; |
| |
| import java.util.ArrayList; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import net.floodlightcontroller.core.FloodlightContext; |
| import net.floodlightcontroller.core.IFloodlightProviderService; |
| import net.floodlightcontroller.core.IOFSwitch; |
| import net.floodlightcontroller.core.module.FloodlightModuleContext; |
| import net.floodlightcontroller.core.test.MockFloodlightProvider; |
| import net.floodlightcontroller.core.test.MockThreadPoolService; |
| import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier; |
| import net.floodlightcontroller.devicemanager.test.MockDeviceManager; |
| import net.floodlightcontroller.counter.CounterStore; |
| import net.floodlightcontroller.counter.ICounterStoreService; |
| import net.floodlightcontroller.devicemanager.IDevice; |
| import net.floodlightcontroller.devicemanager.IDeviceService; |
| import net.floodlightcontroller.devicemanager.IEntityClassifierService; |
| import net.floodlightcontroller.packet.Data; |
| import net.floodlightcontroller.packet.Ethernet; |
| import net.floodlightcontroller.packet.IPacket; |
| import net.floodlightcontroller.packet.IPv4; |
| import net.floodlightcontroller.packet.UDP; |
| import net.floodlightcontroller.routing.IRoutingService; |
| import net.floodlightcontroller.routing.Route; |
| import net.floodlightcontroller.test.FloodlightTestCase; |
| import net.floodlightcontroller.threadpool.IThreadPoolService; |
| import net.floodlightcontroller.topology.ITopologyListener; |
| import net.floodlightcontroller.topology.ITopologyService; |
| import net.floodlightcontroller.topology.NodePortTuple; |
| import net.floodlightcontroller.forwarding.Forwarding; |
| |
| import org.easymock.Capture; |
| import org.easymock.CaptureType; |
| import org.easymock.EasyMock; |
| import org.junit.Test; |
| import org.openflow.protocol.OFFeaturesReply; |
| import org.openflow.protocol.OFFlowMod; |
| import org.openflow.protocol.OFMatch; |
| import org.openflow.protocol.OFMessage; |
| import org.openflow.protocol.OFPacketIn; |
| import org.openflow.protocol.OFPacketOut; |
| import org.openflow.protocol.OFPort; |
| import org.openflow.protocol.OFType; |
| import org.openflow.protocol.OFPacketIn.OFPacketInReason; |
| import org.openflow.protocol.action.OFAction; |
| import org.openflow.protocol.action.OFActionOutput; |
| import org.openflow.util.HexString; |
| |
| public class ForwardingTest extends FloodlightTestCase { |
| protected MockFloodlightProvider mockFloodlightProvider; |
| protected FloodlightContext cntx; |
| protected MockDeviceManager deviceManager; |
| protected IRoutingService routingEngine; |
| protected Forwarding forwarding; |
| protected ITopologyService topology; |
| protected MockThreadPoolService threadPool; |
| protected IOFSwitch sw1, sw2; |
| protected OFFeaturesReply swFeatures; |
| protected IDevice srcDevice, dstDevice1, dstDevice2; |
| protected OFPacketIn packetIn; |
| protected OFPacketOut packetOut; |
| protected OFPacketOut packetOutFlooded; |
| protected IPacket testPacket; |
| protected byte[] testPacketSerialized; |
| protected int expected_wildcards; |
| protected Date currentDate; |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| |
| cntx = new FloodlightContext(); |
| |
| // Module loader setup |
| /* |
| Collection<Class<? extends IFloodlightModule>> mods = new ArrayList<Class<? extends IFloodlightModule>>(); |
| Collection<IFloodlightService> mockedServices = new ArrayList<IFloodlightService>(); |
| mods.add(Forwarding.class); |
| routingEngine = createMock(IRoutingService.class); |
| topology = createMock(ITopologyService.class); |
| mockedServices.add(routingEngine); |
| mockedServices.add(topology); |
| FloodlightTestModuleLoader fml = new FloodlightTestModuleLoader(); |
| fml.setupModules(mods, mockedServices); |
| mockFloodlightProvider = |
| (MockFloodlightProvider) fml.getModuleByName(MockFloodlightProvider.class); |
| deviceManager = |
| (MockDeviceManager) fml.getModuleByName(MockDeviceManager.class); |
| threadPool = |
| (MockThreadPoolService) fml.getModuleByName(MockThreadPoolService.class); |
| forwarding = |
| (Forwarding) fml.getModuleByName(Forwarding.class); |
| */ |
| mockFloodlightProvider = getMockFloodlightProvider(); |
| forwarding = new Forwarding(); |
| threadPool = new MockThreadPoolService(); |
| deviceManager = new MockDeviceManager(); |
| routingEngine = createMock(IRoutingService.class); |
| topology = createMock(ITopologyService.class); |
| DefaultEntityClassifier entityClassifier = new DefaultEntityClassifier(); |
| |
| |
| FloodlightModuleContext fmc = new FloodlightModuleContext(); |
| fmc.addService(IFloodlightProviderService.class, |
| mockFloodlightProvider); |
| fmc.addService(IThreadPoolService.class, threadPool); |
| fmc.addService(ITopologyService.class, topology); |
| fmc.addService(IRoutingService.class, routingEngine); |
| fmc.addService(ICounterStoreService.class, new CounterStore()); |
| fmc.addService(IDeviceService.class, deviceManager); |
| fmc.addService(IEntityClassifierService.class, entityClassifier); |
| |
| topology.addListener(anyObject(ITopologyListener.class)); |
| expectLastCall().anyTimes(); |
| replay(topology); |
| threadPool.init(fmc); |
| forwarding.init(fmc); |
| deviceManager.init(fmc); |
| entityClassifier.init(fmc); |
| threadPool.startUp(fmc); |
| deviceManager.startUp(fmc); |
| forwarding.startUp(fmc); |
| entityClassifier.startUp(fmc); |
| verify(topology); |
| |
| swFeatures = new OFFeaturesReply(); |
| swFeatures.setBuffers(1000); |
| // Mock switches |
| sw1 = EasyMock.createMock(IOFSwitch.class); |
| expect(sw1.getId()).andReturn(1L).anyTimes(); |
| expect(sw1.getBuffers()).andReturn(swFeatures.getBuffers()).anyTimes(); |
| expect(sw1.getStringId()) |
| .andReturn(HexString.toHexString(1L)).anyTimes(); |
| |
| sw2 = EasyMock.createMock(IOFSwitch.class); |
| expect(sw2.getId()).andReturn(2L).anyTimes(); |
| expect(sw2.getBuffers()).andReturn(swFeatures.getBuffers()).anyTimes(); |
| expect(sw2.getStringId()) |
| .andReturn(HexString.toHexString(2L)).anyTimes(); |
| |
| //fastWilcards mocked as this constant |
| int fastWildcards = |
| OFMatch.OFPFW_IN_PORT | |
| OFMatch.OFPFW_NW_PROTO | |
| OFMatch.OFPFW_TP_SRC | |
| OFMatch.OFPFW_TP_DST | |
| OFMatch.OFPFW_NW_SRC_ALL | |
| OFMatch.OFPFW_NW_DST_ALL | |
| OFMatch.OFPFW_NW_TOS; |
| |
| expect(sw1.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).andReturn((Integer)fastWildcards).anyTimes(); |
| expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes(); |
| |
| expect(sw2.getAttribute(IOFSwitch.PROP_FASTWILDCARDS)).andReturn((Integer)fastWildcards).anyTimes(); |
| expect(sw2.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes(); |
| |
| // Load the switch map |
| Map<Long, IOFSwitch> switches = new HashMap<Long, IOFSwitch>(); |
| switches.put(1L, sw1); |
| switches.put(2L, sw2); |
| mockFloodlightProvider.setSwitches(switches); |
| |
| // Build test packet |
| testPacket = new Ethernet() |
| .setDestinationMACAddress("00:11:22:33:44:55") |
| .setSourceMACAddress("00:44:33:22:11:00") |
| .setEtherType(Ethernet.TYPE_IPv4) |
| .setPayload( |
| new IPv4() |
| .setTtl((byte) 128) |
| .setSourceAddress("192.168.1.1") |
| .setDestinationAddress("192.168.1.2") |
| .setPayload(new UDP() |
| .setSourcePort((short) 5000) |
| .setDestinationPort((short) 5001) |
| .setPayload(new Data(new byte[] {0x01})))); |
| |
| |
| |
| currentDate = new Date(); |
| |
| // Mock Packet-in |
| testPacketSerialized = testPacket.serialize(); |
| packetIn = |
| ((OFPacketIn) mockFloodlightProvider.getOFMessageFactory(). |
| getMessage(OFType.PACKET_IN)) |
| .setBufferId(-1) |
| .setInPort((short) 1) |
| .setPacketData(testPacketSerialized) |
| .setReason(OFPacketInReason.NO_MATCH) |
| .setTotalLength((short) testPacketSerialized.length); |
| |
| // Mock Packet-out |
| packetOut = |
| (OFPacketOut) mockFloodlightProvider.getOFMessageFactory(). |
| getMessage(OFType.PACKET_OUT); |
| packetOut.setBufferId(this.packetIn.getBufferId()) |
| .setInPort(this.packetIn.getInPort()); |
| List<OFAction> poactions = new ArrayList<OFAction>(); |
| poactions.add(new OFActionOutput((short) 3, (short) 0xffff)); |
| packetOut.setActions(poactions) |
| .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH) |
| .setPacketData(testPacketSerialized) |
| .setLengthU(OFPacketOut.MINIMUM_LENGTH+ |
| packetOut.getActionsLength()+ |
| testPacketSerialized.length); |
| |
| // Mock Packet-out with OFPP_FLOOD action |
| packetOutFlooded = |
| (OFPacketOut) mockFloodlightProvider.getOFMessageFactory(). |
| getMessage(OFType.PACKET_OUT); |
| packetOutFlooded.setBufferId(this.packetIn.getBufferId()) |
| .setInPort(this.packetIn.getInPort()); |
| poactions = new ArrayList<OFAction>(); |
| poactions.add(new OFActionOutput(OFPort.OFPP_FLOOD.getValue(), |
| (short) 0xffff)); |
| packetOutFlooded.setActions(poactions) |
| .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH) |
| .setPacketData(testPacketSerialized) |
| .setLengthU(OFPacketOut.MINIMUM_LENGTH+ |
| packetOutFlooded.getActionsLength()+ |
| testPacketSerialized.length); |
| |
| expected_wildcards = fastWildcards; |
| expected_wildcards &= ~OFMatch.OFPFW_IN_PORT & |
| ~OFMatch.OFPFW_DL_VLAN & |
| ~OFMatch.OFPFW_DL_SRC & |
| ~OFMatch.OFPFW_DL_DST; |
| expected_wildcards &= ~OFMatch.OFPFW_NW_SRC_MASK & |
| ~OFMatch.OFPFW_NW_DST_MASK; |
| |
| IFloodlightProviderService.bcStore. |
| put(cntx, |
| IFloodlightProviderService.CONTEXT_PI_PAYLOAD, |
| (Ethernet)testPacket); |
| } |
| |
| enum DestDeviceToLearn { NONE, DEVICE1 ,DEVICE2 }; |
| public void learnDevices(DestDeviceToLearn destDeviceToLearn) { |
| // Build src and dest devices |
| byte[] dataLayerSource = ((Ethernet)testPacket).getSourceMACAddress(); |
| byte[] dataLayerDest = |
| ((Ethernet)testPacket).getDestinationMACAddress(); |
| int networkSource = |
| ((IPv4)((Ethernet)testPacket).getPayload()). |
| getSourceAddress(); |
| int networkDest = |
| ((IPv4)((Ethernet)testPacket).getPayload()). |
| getDestinationAddress(); |
| |
| reset(topology); |
| expect(topology.isAttachmentPointPort(1L, (short)1)) |
| .andReturn(true) |
| .anyTimes(); |
| expect(topology.isAttachmentPointPort(2L, (short)3)) |
| .andReturn(true) |
| .anyTimes(); |
| expect(topology.isAttachmentPointPort(1L, (short)3)) |
| .andReturn(true) |
| .anyTimes(); |
| replay(topology); |
| |
| srcDevice = |
| deviceManager.learnEntity(Ethernet.toLong(dataLayerSource), |
| null, networkSource, |
| 1L, 1); |
| IDeviceService.fcStore. put(cntx, |
| IDeviceService.CONTEXT_SRC_DEVICE, |
| srcDevice); |
| if (destDeviceToLearn == DestDeviceToLearn.DEVICE1) { |
| dstDevice1 = |
| deviceManager.learnEntity(Ethernet.toLong(dataLayerDest), |
| null, networkDest, |
| 2L, 3); |
| IDeviceService.fcStore.put(cntx, |
| IDeviceService.CONTEXT_DST_DEVICE, |
| dstDevice1); |
| } |
| if (destDeviceToLearn == DestDeviceToLearn.DEVICE2) { |
| dstDevice2 = |
| deviceManager.learnEntity(Ethernet.toLong(dataLayerDest), |
| null, networkDest, |
| 1L, 3); |
| IDeviceService.fcStore.put(cntx, |
| IDeviceService.CONTEXT_DST_DEVICE, |
| dstDevice2); |
| } |
| verify(topology); |
| } |
| |
| @Test |
| public void testForwardMultiSwitchPath() throws Exception { |
| learnDevices(DestDeviceToLearn.DEVICE1); |
| |
| Capture<OFMessage> wc1 = new Capture<OFMessage>(CaptureType.ALL); |
| Capture<OFMessage> wc2 = new Capture<OFMessage>(CaptureType.ALL); |
| Capture<FloodlightContext> bc1 = |
| new Capture<FloodlightContext>(CaptureType.ALL); |
| Capture<FloodlightContext> bc2 = |
| new Capture<FloodlightContext>(CaptureType.ALL); |
| |
| |
| Route route = new Route(1L, 2L); |
| List<NodePortTuple> nptList = new ArrayList<NodePortTuple>(); |
| nptList.add(new NodePortTuple(1L, (short)1)); |
| nptList.add(new NodePortTuple(1L, (short)3)); |
| nptList.add(new NodePortTuple(2L, (short)1)); |
| nptList.add(new NodePortTuple(2L, (short)3)); |
| route.setPath(nptList); |
| expect(routingEngine.getRoute(1L, (short)1, 2L, (short)3)).andReturn(route).atLeastOnce(); |
| |
| // Expected Flow-mods |
| OFMatch match = new OFMatch(); |
| match.loadFromPacket(testPacketSerialized, (short) 1); |
| OFActionOutput action = new OFActionOutput((short)3, (short)0xffff); |
| List<OFAction> actions = new ArrayList<OFAction>(); |
| actions.add(action); |
| |
| OFFlowMod fm1 = |
| (OFFlowMod) mockFloodlightProvider.getOFMessageFactory(). |
| getMessage(OFType.FLOW_MOD); |
| fm1.setIdleTimeout((short)5) |
| .setMatch(match.clone() |
| .setWildcards(expected_wildcards)) |
| .setActions(actions) |
| .setBufferId(OFPacketOut.BUFFER_ID_NONE) |
| .setCookie(2L << 52) |
| .setLengthU(OFFlowMod.MINIMUM_LENGTH+OFActionOutput.MINIMUM_LENGTH); |
| OFFlowMod fm2 = fm1.clone(); |
| ((OFActionOutput)fm2.getActions().get(0)).setPort((short) 3); |
| |
| sw1.write(capture(wc1), capture(bc1)); |
| expectLastCall().anyTimes(); |
| sw2.write(capture(wc2), capture(bc2)); |
| expectLastCall().anyTimes(); |
| |
| reset(topology); |
| expect(topology.getL2DomainId(1L)).andReturn(1L).anyTimes(); |
| expect(topology.getL2DomainId(2L)).andReturn(1L).anyTimes(); |
| expect(topology.isAttachmentPointPort(1L, (short)1)).andReturn(true).anyTimes(); |
| expect(topology.isAttachmentPointPort(2L, (short)3)).andReturn(true).anyTimes(); |
| expect(topology.isIncomingBroadcastAllowed(anyLong(), anyShort())).andReturn(true).anyTimes(); |
| |
| // Reset mocks, trigger the packet in, and validate results |
| replay(sw1, sw2, routingEngine, topology); |
| forwarding.receive(sw1, this.packetIn, cntx); |
| verify(sw1, sw2, routingEngine); |
| |
| assertTrue(wc1.hasCaptured()); // wc1 should get packetout + flowmod. |
| assertTrue(wc2.hasCaptured()); // wc2 should be a flowmod. |
| |
| List<OFMessage> msglist = wc1.getValues(); |
| |
| for (OFMessage m: msglist) { |
| if (m instanceof OFFlowMod) |
| assertEquals(fm1, m); |
| else if (m instanceof OFPacketOut) |
| assertEquals(packetOut, m); |
| } |
| |
| OFMessage m = wc2.getValue(); |
| assert (m instanceof OFFlowMod); |
| assertTrue(m.equals(fm2)); |
| } |
| |
| @Test |
| public void testForwardSingleSwitchPath() throws Exception { |
| learnDevices(DestDeviceToLearn.DEVICE2); |
| |
| Route route = new Route(1L, 1L); |
| route.getPath().add(new NodePortTuple(1L, (short)1)); |
| route.getPath().add(new NodePortTuple(1L, (short)3)); |
| expect(routingEngine.getRoute(1L, (short)1, 1L, (short)3)).andReturn(route).atLeastOnce(); |
| |
| // Expected Flow-mods |
| OFMatch match = new OFMatch(); |
| match.loadFromPacket(testPacketSerialized, (short) 1); |
| OFActionOutput action = new OFActionOutput((short)3, (short)0xffff); |
| List<OFAction> actions = new ArrayList<OFAction>(); |
| actions.add(action); |
| |
| OFFlowMod fm1 = |
| (OFFlowMod) mockFloodlightProvider.getOFMessageFactory(). |
| getMessage(OFType.FLOW_MOD); |
| fm1.setIdleTimeout((short)5) |
| .setMatch(match.clone() |
| .setWildcards(expected_wildcards)) |
| .setActions(actions) |
| .setBufferId(OFPacketOut.BUFFER_ID_NONE) |
| .setCookie(2L << 52) |
| .setLengthU(OFFlowMod.MINIMUM_LENGTH + |
| OFActionOutput.MINIMUM_LENGTH); |
| |
| // Record expected packet-outs/flow-mods |
| sw1.write(fm1, cntx); |
| sw1.write(packetOut, cntx); |
| |
| reset(topology); |
| expect(topology.isIncomingBroadcastAllowed(anyLong(), anyShort())).andReturn(true).anyTimes(); |
| expect(topology.getL2DomainId(1L)).andReturn(1L).anyTimes(); |
| expect(topology.isAttachmentPointPort(1L, (short)1)).andReturn(true).anyTimes(); |
| expect(topology.isAttachmentPointPort(1L, (short)3)).andReturn(true).anyTimes(); |
| |
| // Reset mocks, trigger the packet in, and validate results |
| replay(sw1, sw2, routingEngine, topology); |
| forwarding.receive(sw1, this.packetIn, cntx); |
| verify(sw1, sw2, routingEngine); |
| } |
| |
| @Test |
| public void testFlowModDampening() throws Exception { |
| learnDevices(DestDeviceToLearn.DEVICE2); |
| |
| reset(topology); |
| expect(topology.isAttachmentPointPort(EasyMock.anyLong(), EasyMock.anyShort())) |
| .andReturn(true).anyTimes(); |
| expect(topology.getL2DomainId(1L)).andReturn(1L).anyTimes(); |
| replay(topology); |
| |
| |
| Route route = new Route(1L, 1L); |
| route.getPath().add(new NodePortTuple(1L, (short)1)); |
| route.getPath().add(new NodePortTuple(1L, (short)3)); |
| expect(routingEngine.getRoute(1L, (short)1, 1L, (short)3)).andReturn(route).atLeastOnce(); |
| |
| // Expected Flow-mods |
| OFMatch match = new OFMatch(); |
| match.loadFromPacket(testPacketSerialized, (short) 1); |
| OFActionOutput action = new OFActionOutput((short)3, (short)0xffff); |
| List<OFAction> actions = new ArrayList<OFAction>(); |
| actions.add(action); |
| |
| OFFlowMod fm1 = |
| (OFFlowMod) mockFloodlightProvider.getOFMessageFactory(). |
| getMessage(OFType.FLOW_MOD); |
| fm1.setIdleTimeout((short)5) |
| .setMatch(match.clone() |
| .setWildcards(expected_wildcards)) |
| .setActions(actions) |
| .setBufferId(OFPacketOut.BUFFER_ID_NONE) |
| .setCookie(2L << 52) |
| .setLengthU(OFFlowMod.MINIMUM_LENGTH + |
| OFActionOutput.MINIMUM_LENGTH); |
| |
| // Record expected packet-outs/flow-mods |
| // We will inject the packet_in 3 times and expect 1 flow mod and |
| // 3 packet outs due to flow mod dampening |
| sw1.write(fm1, cntx); |
| expectLastCall().once(); |
| sw1.write(packetOut, cntx); |
| expectLastCall().times(3); |
| |
| reset(topology); |
| expect(topology.isIncomingBroadcastAllowed(anyLong(), anyShort())).andReturn(true).anyTimes(); |
| expect(topology.getL2DomainId(1L)).andReturn(1L).anyTimes(); |
| expect(topology.isAttachmentPointPort(1L, (short)1)).andReturn(true).anyTimes(); |
| expect(topology.isAttachmentPointPort(1L, (short)3)).andReturn(true).anyTimes(); |
| |
| // Reset mocks, trigger the packet in, and validate results |
| replay(sw1, routingEngine, topology); |
| forwarding.receive(sw1, this.packetIn, cntx); |
| forwarding.receive(sw1, this.packetIn, cntx); |
| forwarding.receive(sw1, this.packetIn, cntx); |
| verify(sw1, routingEngine); |
| } |
| |
| @Test |
| public void testForwardNoPath() throws Exception { |
| learnDevices(DestDeviceToLearn.NONE); |
| |
| // Set no destination attachment point or route |
| // expect no Flow-mod but expect the packet to be flooded |
| |
| // Reset mocks, trigger the packet in, and validate results |
| reset(topology); |
| expect(topology.isIncomingBroadcastAllowed(1L, (short)1)).andReturn(true).anyTimes(); |
| expect(topology.isAttachmentPointPort(EasyMock.anyLong(), |
| EasyMock.anyShort())) |
| .andReturn(true) |
| .anyTimes(); |
| expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_FLOOD)) |
| .andReturn(true).anyTimes(); |
| sw1.write(packetOutFlooded, cntx); |
| expectLastCall().once(); |
| replay(sw1, sw2, routingEngine, topology); |
| forwarding.receive(sw1, this.packetIn, cntx); |
| verify(sw1, sw2, routingEngine); |
| } |
| |
| } |