/** * 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.flowcache.FlowReconcileManager; import net.floodlightcontroller.flowcache.IFlowReconcileService; 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 FlowReconcileManager flowReconcileMgr; 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(); flowReconcileMgr = new FlowReconcileManager(); 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(IFlowReconcileService.class, flowReconcileMgr); fmc.addService(IEntityClassifierService.class, entityClassifier); topology.addListener(anyObject(ITopologyListener.class)); expectLastCall().anyTimes(); replay(topology); threadPool.init(fmc); forwarding.init(fmc); deviceManager.init(fmc); flowReconcileMgr.init(fmc); entityClassifier.init(fmc); threadPool.startUp(fmc); deviceManager.startUp(fmc); forwarding.startUp(fmc); flowReconcileMgr.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); } }