diff --git a/.gitignore b/.gitignore index 82d143f44f3b0707dbc09ebdf82c6c015569dbc4..0c0e028da7769c5a0bf218c9f98d29ea1b990a43 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ findbugs-results *.idea/* *.iml .metadata/ +/bin/ diff --git a/src/main/java/net/floodlightcontroller/core/util/AppCookie.java b/src/main/java/net/floodlightcontroller/core/util/AppCookie.java index 5406bbd8c9a6a2b92b423db0598d225713b83fa4..d35e6a9e56631e21f3c13f52db9b6f693199fe76 100644 --- a/src/main/java/net/floodlightcontroller/core/util/AppCookie.java +++ b/src/main/java/net/floodlightcontroller/core/util/AppCookie.java @@ -69,6 +69,20 @@ public class AppCookie { private static ConcurrentMap<Integer, String> appIdMap = new ConcurrentHashMap<Integer, String>(); + /** + * Returns a mask suitable for matching the App ID within a cookie. + */ + static public U64 getAppFieldMask() { + return U64.of(APP_ID_MASK << APP_ID_SHIFT); + } + + /** + * Returns a mask suitable for matching the User field within a cookie. + */ + static public U64 getUserFieldMask() { + return U64.of(USER_MASK); + } + /** * Encapsulate an application ID and a user block of stuff into a cookie * diff --git a/src/main/java/net/floodlightcontroller/firewall/Firewall.java b/src/main/java/net/floodlightcontroller/firewall/Firewall.java index 5b63189012de1a7aa544816ac4823673e79d438a..4ecc605c40e359ea828e3d380d2b18b9cc1886b6 100644 --- a/src/main/java/net/floodlightcontroller/firewall/Firewall.java +++ b/src/main/java/net/floodlightcontroller/firewall/Firewall.java @@ -37,8 +37,10 @@ import org.projectfloodlight.openflow.types.IPv4Address; import org.projectfloodlight.openflow.types.IPv4AddressWithMask; import org.projectfloodlight.openflow.types.IpProtocol; import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.Masked; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.TransportPort; +import org.projectfloodlight.openflow.types.U64; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IOFMessageListener; @@ -47,6 +49,7 @@ import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; import net.floodlightcontroller.core.module.IFloodlightService; +import net.floodlightcontroller.core.util.AppCookie; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.devicemanager.IDeviceService; @@ -58,6 +61,7 @@ import net.floodlightcontroller.packet.TCP; import net.floodlightcontroller.packet.UDP; import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.routing.IRoutingDecision; +import net.floodlightcontroller.routing.IRoutingService; import net.floodlightcontroller.routing.RoutingDecision; import net.floodlightcontroller.storage.IResultSet; import net.floodlightcontroller.storage.IStorageSourceService; @@ -66,6 +70,8 @@ import net.floodlightcontroller.storage.StorageException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableList; + /** * Stateless firewall implemented as a Google Summer of Code project. * Configuration done through REST API @@ -76,11 +82,20 @@ import org.slf4j.LoggerFactory; */ public class Firewall implements IFirewallService, IOFMessageListener, IFloodlightModule { + private static final short APP_ID = 30; + static { + AppCookie.registerApp(APP_ID, "Firewall"); + } + private static final U64 DENY_BCAST_COOKIE = AppCookie.makeCookie(APP_ID, 0xaaaaaaaa); + private static final U64 ALLOW_BCAST_COOKIE = AppCookie.makeCookie(APP_ID, 0x55555555); + private static final U64 RULE_MISS_COOKIE = AppCookie.makeCookie(APP_ID, -1); + static final U64 DEFAULT_COOKIE = AppCookie.makeCookie(APP_ID, 0); // service modules needed protected IFloodlightProviderService floodlightProvider; protected IStorageSourceService storageSource; protected IRestApiService restApi; + protected IRoutingService routingService; protected static Logger logger; protected List<FirewallRule> rules; // protected by synchronized @@ -161,6 +176,7 @@ IFloodlightModule { l.add(IFloodlightProviderService.class); l.add(IStorageSourceService.class); l.add(IRestApiService.class); + l.add(IRoutingService.class); return l; } @@ -279,6 +295,7 @@ IFloodlightModule { floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class); storageSource = context.getServiceImpl(IStorageSourceService.class); restApi = context.getServiceImpl(IRestApiService.class); + routingService = context.getServiceImpl(IRoutingService.class); rules = new ArrayList<FirewallRule>(); logger = LoggerFactory.getLogger(Firewall.class); @@ -309,7 +326,9 @@ IFloodlightModule { switch (msg.getType()) { case PACKET_IN: IRoutingDecision decision = null; - if (cntx != null) { + if (cntx == null) { + logger.warn("Firewall unable to request packet drop: FloodlightContext is null."); + } else { decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); return this.processPacketInMessage(sw, (OFPacketIn) msg, decision, cntx); } @@ -323,8 +342,17 @@ IFloodlightModule { @Override public void enableFirewall(boolean enabled) { - logger.info("Setting firewall to {}", enabled); - this.enabled = enabled; + if(this.enabled != enabled) { + logger.info("Setting firewall to {}", enabled); + this.enabled = enabled; + + List<Masked<U64>> changes = ImmutableList.of( + Masked.of(DEFAULT_COOKIE, AppCookie.getAppFieldMask()) + ); + + // Add announcement that all firewall decisions changed + routingService.handleRoutingDecisionChange(changes); + } } @Override @@ -412,6 +440,18 @@ IFloodlightModule { entry.put(COLUMN_PRIORITY, Integer.toString(rule.priority)); entry.put(COLUMN_ACTION, Integer.toString(rule.action.ordinal())); storageSource.insertRow(TABLE_NAME, entry); + + U64 singleRuleMask = AppCookie.getAppFieldMask().or(AppCookie.getUserFieldMask()); + ImmutableList.Builder<Masked<U64>> changesBuilder = ImmutableList.builder(); + Iterator<FirewallRule> iter = this.rules.iterator(); + while (iter.hasNext()) { + FirewallRule r = iter.next(); + if (r.priority >= rule.priority) { + changesBuilder.add(Masked.of(AppCookie.makeCookie(APP_ID, r.ruleid), singleRuleMask)); + } + } + changesBuilder.add(Masked.of(RULE_MISS_COOKIE, singleRuleMask)); + routingService.handleRoutingDecisionChange(changesBuilder.build()); } @Override @@ -427,6 +467,17 @@ IFloodlightModule { } // delete from database storageSource.deleteRow(TABLE_NAME, Integer.toString(ruleid)); + + //Add announcement that the rule has been deleted + Masked<U64> delDescriptor = Masked.of( + AppCookie.makeCookie(APP_ID, ruleid), + AppCookie.getAppFieldMask().or(AppCookie.getUserFieldMask())); + + List<Masked<U64>> changes = ImmutableList.of(delDescriptor); + + //Add announcement that rule is added + // should we try to delete the flow even if not found in this.rules + routingService.handleRoutingDecisionChange(changes); } /** @@ -560,6 +611,7 @@ IFloodlightModule { decision = new RoutingDecision(sw.getId(), inPort, IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_SRC_DEVICE), IRoutingDecision.RoutingAction.MULTICAST); + decision.setDescriptor(ALLOW_BCAST_COOKIE); decision.addToContext(cntx); } else { if (logger.isTraceEnabled()) { @@ -569,6 +621,7 @@ IFloodlightModule { decision = new RoutingDecision(sw.getId(), inPort, IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_SRC_DEVICE), IRoutingDecision.RoutingAction.DROP); + decision.setDescriptor(DENY_BCAST_COOKIE); decision.addToContext(cntx); } return Command.CONTINUE; @@ -581,6 +634,7 @@ IFloodlightModule { * else if (eth.getEtherType() == Ethernet.TYPE_ARP) { * logger.info("allowing ARP traffic"); decision = new * FirewallDecision(IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD); + * decision.setDescriptor(ALLOW_BCAST_COOKIE); * decision.addToContext(cntx); return Command.CONTINUE; } */ @@ -596,6 +650,11 @@ IFloodlightModule { IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_SRC_DEVICE), IRoutingDecision.RoutingAction.DROP); decision.setMatch(rmp.match); + if (rule == null) { + decision.setDescriptor(RULE_MISS_COOKIE); + } else { + decision.setDescriptor(AppCookie.makeCookie(APP_ID, rule.ruleid)); + } decision.addToContext(cntx); if (logger.isTraceEnabled()) { if (rule == null) { @@ -610,6 +669,7 @@ IFloodlightModule { IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_SRC_DEVICE), IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD); decision.setMatch(rmp.match); + decision.setDescriptor(AppCookie.makeCookie(APP_ID, rule.ruleid)); decision.addToContext(cntx); if (logger.isTraceEnabled()) { logger.trace("Allow rule={} match for PacketIn={}", rule, pi); diff --git a/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java b/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java index ca041e49a4f5320d163491524cecea400ae6d002..87ffc5500174a7f7d96483be1ec4bb21fe0890f2 100644 --- a/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java +++ b/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java @@ -50,6 +50,7 @@ import net.floodlightcontroller.packet.TCP; import net.floodlightcontroller.packet.UDP; import net.floodlightcontroller.routing.ForwardingBase; import net.floodlightcontroller.routing.IRoutingDecision; +import net.floodlightcontroller.routing.IRoutingDecisionChangedListener; import net.floodlightcontroller.routing.IRoutingService; import net.floodlightcontroller.routing.Route; import net.floodlightcontroller.topology.ITopologyService; @@ -77,6 +78,7 @@ import org.projectfloodlight.openflow.types.IPv4Address; import org.projectfloodlight.openflow.types.IPv6Address; import org.projectfloodlight.openflow.types.IpProtocol; import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.Masked; import org.projectfloodlight.openflow.types.OFBufferId; import org.projectfloodlight.openflow.types.OFGroup; import org.projectfloodlight.openflow.types.OFPort; @@ -84,12 +86,18 @@ import org.projectfloodlight.openflow.types.OFVlanVidMatch; import org.projectfloodlight.openflow.types.TableId; import org.projectfloodlight.openflow.types.U64; import org.projectfloodlight.openflow.types.VlanVid; +import org.python.google.common.collect.ImmutableList; +import org.python.google.common.collect.Maps; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class Forwarding extends ForwardingBase implements IFloodlightModule, IOFSwitchListener, ILinkDiscoveryListener { +public class Forwarding extends ForwardingBase implements IFloodlightModule, IOFSwitchListener, ILinkDiscoveryListener, IRoutingDecisionChangedListener { protected static Logger log = LoggerFactory.getLogger(Forwarding.class); - + final static U64 DEFAULT_FORWARDING_COOKIE = AppCookie.makeCookie(FORWARDING_APP_ID, 0); + + // This mask determines how much of each cookie will contain IRoutingDecision descriptor bits. + private final long DECISION_MASK = 0x00000000ffffffffL; // TODO: shrink this mask if you need to add more sub-fields. + @Override public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) { Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); @@ -105,11 +113,11 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF return Command.CONTINUE; case FORWARD_OR_FLOOD: case FORWARD: - doForwardFlow(sw, pi, cntx, false); + doForwardFlow(sw, pi, decision, cntx, false); return Command.CONTINUE; case MULTICAST: // treat as broadcast - doFlood(sw, pi, cntx); + doFlood(sw, pi, decision, cntx); return Command.CONTINUE; case DROP: doDropFlow(sw, pi, decision, cntx); @@ -124,21 +132,124 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF } if (eth.isBroadcast() || eth.isMulticast()) { - doFlood(sw, pi, cntx); + doFlood(sw, pi, decision, cntx); } else { - doForwardFlow(sw, pi, cntx, false); + doForwardFlow(sw, pi, decision, cntx, false); } } return Command.CONTINUE; } + + /** + * Builds a cookie that includes routing decision information. + * + * @param decision The routing decision providing a descriptor, or null + * @return A cookie with our app id and the required routing fields masked-in + */ + protected U64 makeForwardingCookie(IRoutingDecision decision) { + int user_fields = 0; + + U64 decision_cookie = (decision == null) ? null : decision.getDescriptor(); + if (decision_cookie != null) { + user_fields |= AppCookie.extractUser(decision_cookie) & DECISION_MASK; + } + + // TODO: Mask in any other required fields here (e.g. link failure flowset ID) + + if (user_fields == 0) { + return DEFAULT_FORWARDING_COOKIE; + } + return AppCookie.makeCookie(FORWARDING_APP_ID, user_fields); + } + + /** Called when the handleDecisionChange is triggered by an event (routing decision was changed in firewall). + * + * @param changedDecisions Masked routing descriptors for flows that should be deleted from the switch. + */ + @Override + public void routingDecisionChanged(Iterable<Masked<U64>> changedDecisions) { + deleteFlowsByDescriptor(changedDecisions); + } + + /** + * Converts a sequence of masked IRoutingDecision descriptors into masked Forwarding cookies. + * + * This generates a list of masked cookies that can then be matched in flow-mod messages. + * + * @param maskedDescriptors A sequence of masked cookies describing IRoutingDecision descriptors + * @return A collection of masked cookies suitable for flow-mod operations + */ + protected Collection<Masked<U64>> convertRoutingDecisionDescriptors(Iterable<Masked<U64>> maskedDescriptors) { + if(maskedDescriptors == null) { + return null; + } + + ImmutableList.Builder<Masked<U64>> resultBuilder = ImmutableList.builder(); + for (Masked<U64> maskedDescriptor : maskedDescriptors) { + long user_mask = AppCookie.extractUser(maskedDescriptor.getMask()) & DECISION_MASK; + long user_value = AppCookie.extractUser(maskedDescriptor.getValue()) & user_mask; + + // TODO combine in any other cookie fields you need here. + + resultBuilder.add( + Masked.of( + AppCookie.makeCookie(FORWARDING_APP_ID, (int)user_value), + AppCookie.getAppFieldMask().or(U64.of(user_mask)) + ) + ); + } + + return resultBuilder.build(); + } + + /** + * On all active switches, deletes all flows matching the IRoutingDecision descriptors provided + * as arguments. + * + * @param descriptors The descriptors and masks describing which flows to delete. + */ + protected void deleteFlowsByDescriptor(Iterable<Masked<U64>> descriptors) { + Collection<Masked<U64>> masked_cookies = convertRoutingDecisionDescriptors(descriptors); + + if (masked_cookies != null && !masked_cookies.isEmpty()) { + Map<OFVersion, List<OFMessage>> cache = Maps.newHashMap(); + + for (DatapathId dpid : switchService.getAllSwitchDpids()) { + IOFSwitch sw = switchService.getActiveSwitch(dpid); + if (sw == null) { + continue; + } + + OFVersion ver = sw.getOFFactory().getVersion(); + if (cache.containsKey(ver)) { + sw.write(cache.get(ver)); + } else { + ImmutableList.Builder<OFMessage> msgsBuilder = ImmutableList.builder(); + for (Masked<U64> masked_cookie : masked_cookies) { + msgsBuilder.add( + sw.getOFFactory().buildFlowDelete() + .setCookie(masked_cookie.getValue()) + .setCookieMask(masked_cookie.getMask()) + .build() + ); + } + + List<OFMessage> msgs = msgsBuilder.build(); + sw.write(msgs); + cache.put(ver, msgs); + } + } + } + } + protected void doDropFlow(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) { OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)); Match m = createMatchFromPacket(sw, inPort, cntx); OFFlowMod.Builder fmb = sw.getOFFactory().buildFlowAdd(); // this will be a drop-flow; a flow that will not output to any ports List<OFAction> actions = new ArrayList<OFAction>(); // set no action to drop - U64 cookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0); + U64 cookie = makeForwardingCookie(decision); log.info("Dropping"); fmb.setCookie(cookie) .setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT) @@ -162,7 +273,7 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF log.debug("OFMessage dampened: {}", dampened); } - protected void doForwardFlow(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx, boolean requestFlowRemovedNotifn) { + protected void doForwardFlow(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx, boolean requestFlowRemovedNotifn) { OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)); IDevice dstDevice = IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_DST_DEVICE); DatapathId source = sw.getId(); @@ -179,7 +290,7 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD).getEtherType() == EthType.ARP) { log.debug("ARP flows disabled in Forwarding. Flooding ARP packet"); - doFlood(sw, pi, cntx); + doFlood(sw, pi, decision, cntx); return; } @@ -226,24 +337,24 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF */ if (dstDap == null) { log.warn("Could not locate edge attachment point for device {}. Flooding packet"); - doFlood(sw, pi, cntx); + doFlood(sw, pi, decision, cntx); return; } /* It's possible that we learned packed destination while it was in flight */ if (!topologyService.isEdge(source, inPort)) { log.debug("Packet destination is known, but packet was not received on an edge port (rx on {}/{}). Flooding packet", source, inPort); - doFlood(sw, pi, cntx); + doFlood(sw, pi, decision, cntx); return; } + U64 cookie = makeForwardingCookie(decision); Route route = routingEngineService.getRoute(source, inPort, dstDap.getNodeId(), - dstDap.getPortId(), U64.of(0)); //cookie = 0, i.e., default route + dstDap.getPortId(), cookie); // Cookie currently ignored. May carry useful info in the future. Match m = createMatchFromPacket(sw, inPort, cntx); - U64 cookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0); if (route != null) { if (log.isDebugEnabled()) { @@ -275,7 +386,7 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF } } else { log.debug("Destination unknown. Flooding packet"); - doFlood(sw, pi, cntx); + doFlood(sw, pi, decision, cntx); } } @@ -388,9 +499,10 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF * the port is blocked, in which case the packet will be dropped. * @param sw The switch that receives the OFPacketIn * @param pi The OFPacketIn that came to the switch + * @param decision The decision that caused flooding, or null * @param cntx The FloodlightContext associated with this OFPacketIn */ - protected void doFlood(IOFSwitch sw, OFPacketIn pi, FloodlightContext cntx) { + protected void doFlood(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) { OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)); // Set Action to flood OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut(); @@ -543,6 +655,7 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF public void startUp(FloodlightModuleContext context) { super.startUp(); switchService.addOFSwitchListener(this); + routingEngineService.addRoutingDecisionChangedListener(this); /* Register only if we want to remove stale flows */ if (REMOVE_FLOWS_ON_LINK_OR_PORT_DOWN) { @@ -621,14 +734,16 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF if (srcSw != null) { /* flows matching on src port */ msgs.add(srcSw.getOFFactory().buildFlowDelete() - .setCookie(AppCookie.makeCookie(FORWARDING_APP_ID, 0)) + .setCookie(DEFAULT_FORWARDING_COOKIE) + .setCookieMask(AppCookie.getAppFieldMask()) .setMatch(srcSw.getOFFactory().buildMatch() .setExact(MatchField.IN_PORT, u.getSrcPort()) .build()) .build()); /* flows outputting to src port */ msgs.add(srcSw.getOFFactory().buildFlowDelete() - .setCookie(AppCookie.makeCookie(FORWARDING_APP_ID, 0)) + .setCookie(DEFAULT_FORWARDING_COOKIE) + .setCookieMask(AppCookie.getAppFieldMask()) .setOutPort(u.getSrcPort()) .build()); messageDamper.write(srcSw, msgs); @@ -644,14 +759,16 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF /* flows matching on dst port */ msgs.clear(); msgs.add(dstSw.getOFFactory().buildFlowDelete() - .setCookie(AppCookie.makeCookie(FORWARDING_APP_ID, 0)) + .setCookie(DEFAULT_FORWARDING_COOKIE) + .setCookieMask(AppCookie.getAppFieldMask()) .setMatch(dstSw.getOFFactory().buildMatch() .setExact(MatchField.IN_PORT, u.getDstPort()) .build()) .build()); /* flows outputting to dst port */ msgs.add(dstSw.getOFFactory().buildFlowDelete() - .setCookie(AppCookie.makeCookie(FORWARDING_APP_ID, 0)) + .setCookie(DEFAULT_FORWARDING_COOKIE) + .setCookieMask(AppCookie.getAppFieldMask()) .setOutPort(u.getDstPort()) .build()); messageDamper.write(dstSw, msgs); diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java index 7e933000316c2c2a625d816ee69a21404308f402..a4b835724baf922e6acbe14dd03e8a829f0aee67 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java @@ -325,7 +325,7 @@ IFloodlightModule, IInfoProvider { byte[] srcMac = ofpPort.getHwAddr().getBytes(); byte[] zeroMac = { 0, 0, 0, 0, 0, 0 }; if (Arrays.equals(srcMac, zeroMac)) { - log.warn("Port {}/{} has zero hareware address" + log.warn("Port {}/{} has zero hardware address" + "overwrite with lower 6 bytes of dpid", dpid.toString(), ofpPort.getPortNo().getPortNumber()); System.arraycopy(dpidArray, 2, srcMac, 0, 6); diff --git a/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java b/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java index 6af24c6e35a14bdcbc141c24ad020d4423f7de13..592623fb89b63a758d119833833c9078ac27b67b 100644 --- a/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java +++ b/src/main/java/net/floodlightcontroller/routing/IRoutingDecision.java @@ -20,6 +20,7 @@ package net.floodlightcontroller.routing; import java.util.List; import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.types.U64; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.FloodlightContextStore; @@ -63,4 +64,7 @@ public interface IRoutingDecision { public void setMatch(Match match); public int getHardTimeout(); public void setHardTimeout(short hardTimeout); + public U64 getDescriptor(); + public void setDescriptor(U64 descriptor); + } diff --git a/src/main/java/net/floodlightcontroller/routing/IRoutingDecisionChangedListener.java b/src/main/java/net/floodlightcontroller/routing/IRoutingDecisionChangedListener.java new file mode 100644 index 0000000000000000000000000000000000000000..c82b420c8c9469cf08c90eca9871c0ed5a479160 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/routing/IRoutingDecisionChangedListener.java @@ -0,0 +1,18 @@ +package net.floodlightcontroller.routing; + +import org.projectfloodlight.openflow.types.Masked; +import org.projectfloodlight.openflow.types.U64; + +public interface IRoutingDecisionChangedListener { + + /** Notifies the listener that routing logic has changed, requiring certain past routing decisions + * to become invalid. The caller provides a sequence of masked values that match against + * past values of IRoutingDecision.getDescriptor(). Services that have operated on past + * routing decisions are then able to remove the results of past decisions, normally by deleting + * flows. + * + * @param changedDecisions Masked descriptors identifying routing decisions that are now obsolete or invalid + */ + public void routingDecisionChanged(Iterable<Masked<U64>> changedDecisions); + +} diff --git a/src/main/java/net/floodlightcontroller/routing/IRoutingService.java b/src/main/java/net/floodlightcontroller/routing/IRoutingService.java index fde607b89cef1ca00a78e8867748c0c2f4c2c795..c773cdfcf153cb05e70bef454fcfe9fbfe041bd4 100644 --- a/src/main/java/net/floodlightcontroller/routing/IRoutingService.java +++ b/src/main/java/net/floodlightcontroller/routing/IRoutingService.java @@ -20,6 +20,7 @@ package net.floodlightcontroller.routing; import java.util.ArrayList; import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.Masked; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.U64; @@ -82,5 +83,25 @@ public interface IRoutingService extends IFloodlightService { * or not have tunnels as part of the path. */ public boolean routeExists(DatapathId src, DatapathId dst, boolean tunnelEnabled); + + /** Register the RDCListener + * @param listener - The module that wants to listen for events + */ + public void addRoutingDecisionChangedListener(IRoutingDecisionChangedListener listener); + + /** Remove the RDCListener + * @param listener - The module that wants to stop listening for events + */ + public void removeRoutingDecisionChangedListener(IRoutingDecisionChangedListener listener); + + /** Notifies listeners that routing logic has changed, requiring certain past routing decisions + * to become invalid. The caller provides a sequence of masked values that match against + * past values of IRoutingDecision.getDescriptor(). Services that have operated on past + * routing decisions are then able to remove the results of past decisions, normally by deleting + * flows. + * + * @param changedDecisions Masked descriptors identifying routing decisions that are now obsolete or invalid + */ + public void handleRoutingDecisionChange(Iterable<Masked<U64>> changedDecisions); } diff --git a/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java b/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java index e72ccfc6eb56ce2652b013efbcb64fc2e4b6d4cc..8ce5c5c9415fcd14a3dcd34de0264b719b5536f7 100644 --- a/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java +++ b/src/main/java/net/floodlightcontroller/routing/RoutingDecision.java @@ -23,6 +23,7 @@ import java.util.List; import org.projectfloodlight.openflow.protocol.match.Match; import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.U64; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.devicemanager.IDevice; @@ -38,6 +39,7 @@ public class RoutingDecision implements IRoutingDecision { protected IDevice srcDevice; protected List<IDevice> destDevices; protected List<SwitchPort> broadcastIntertfaces; + protected U64 descriptor; public RoutingDecision(DatapathId swDipd, OFPort inPort, @@ -114,6 +116,16 @@ public class RoutingDecision implements IRoutingDecision { this.hardTimeout = hardTimeout; } + @Override + public U64 getDescriptor() { + return descriptor; + } + + @Override + public void setDescriptor(U64 descriptor) { + this.descriptor = descriptor; + } + @Override public void addToContext(FloodlightContext cntx) { rtStore.put(cntx, IRoutingDecision.CONTEXT_DECISION, this); diff --git a/src/main/java/net/floodlightcontroller/topology/TopologyManager.java b/src/main/java/net/floodlightcontroller/topology/TopologyManager.java index 648910916ed36c529e184a65113c44e8a4463fc8..39211be7371b7729fbe75336c09111c4650948d4 100644 --- a/src/main/java/net/floodlightcontroller/topology/TopologyManager.java +++ b/src/main/java/net/floodlightcontroller/topology/TopologyManager.java @@ -32,6 +32,7 @@ import net.floodlightcontroller.packet.BSN; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.packet.LLDP; import net.floodlightcontroller.restserver.IRestApiService; +import net.floodlightcontroller.routing.IRoutingDecisionChangedListener; import net.floodlightcontroller.routing.IRoutingService; import net.floodlightcontroller.routing.Link; import net.floodlightcontroller.routing.Route; @@ -41,6 +42,7 @@ import org.projectfloodlight.openflow.protocol.*; import org.projectfloodlight.openflow.protocol.action.OFAction; import org.projectfloodlight.openflow.protocol.match.MatchField; import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.Masked; import org.projectfloodlight.openflow.types.OFBufferId; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.U64; @@ -135,6 +137,9 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, IRo */ protected static final String PACKAGE = TopologyManager.class.getPackage().getName(); protected IDebugCounter ctrIncoming; + + /** Array list that contains all of the decisionChangedListeners */ + protected ArrayList<IRoutingDecisionChangedListener> decisionChangedListeners; // Getter/Setter methods /** @@ -605,6 +610,41 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, IRo result.add(getRoute(srcDpid, dstDpid, U64.of(0), tunnelEnabled)); return result; } + + /** + * Registers an IRoutingDecisionChangedListener. + * + * @param {IRoutingDecisionChangedListener} listener - + * @return {void} + */ + @Override + public void addRoutingDecisionChangedListener(IRoutingDecisionChangedListener listener) { + decisionChangedListeners.add(listener); + } + + /** + * Deletes an IRoutingDecisionChangedListener. + * + * @param {IRoutingDecisionChangedListener} listener - + * @return {void} + */ + @Override + public void removeRoutingDecisionChangedListener(IRoutingDecisionChangedListener listener) { + decisionChangedListeners.remove(listener); + } + + /** + * Listens for the event to the IRoutingDecisionChanged listener and calls routingDecisionChanged(). + * + * @param {Iterable<Masked<U64>>} - event + * @return {void} + */ + @Override + public void handleRoutingDecisionChange(Iterable<Masked<U64>> changedDecisions) { + for(IRoutingDecisionChangedListener listener : decisionChangedListeners) { + listener.routingDecisionChanged(changedDecisions); + } + } // ****************** // IOFMessageListener @@ -743,6 +783,7 @@ public class TopologyManager implements IFloodlightModule, ITopologyService, IRo topologyAware = new ArrayList<ITopologyListener>(); ldUpdates = new LinkedBlockingQueue<LDUpdate>(); haListener = new HAListenerDelegate(); + this.decisionChangedListeners = new ArrayList<IRoutingDecisionChangedListener>(); registerTopologyDebugCounters(); } diff --git a/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java b/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java index 5e0f87538b0a7f850446a826420418cdaa67e352..5d0e22b6447ebcd88ce8c937d4ea430f1e053ee8 100644 --- a/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java +++ b/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java @@ -109,4 +109,17 @@ public class AppCookieTest { } + + @Test + public void testAppFieldMask(){ + final int myAppId = 71; + AppCookie.registerApp(myAppId, "TestFieldMask"); + U64 result = AppCookie.getAppFieldMask(); + U64 expectedMask = U64.of(0xfff0000000000000L); + assertEquals(expectedMask, result); + + U64 cookie = AppCookie.makeCookie(myAppId, -1); + U64 maskAppField = cookie.and(result); + assertEquals(U64.of(0x470000000000000L),maskAppField); + } } diff --git a/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java b/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java index 9c0daa5c9b9f5e02101e86c6b43027e5252b1ca8..027d688b32d8415de450c80c2b619cbe3a4afe50 100644 --- a/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java +++ b/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java @@ -17,11 +17,15 @@ package net.floodlightcontroller.firewall; +import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; +import static org.easymock.EasyMock.capture; +import static org.easymock.EasyMock.reset; import static org.junit.Assert.*; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,6 +35,7 @@ import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.core.module.FloodlightModuleContext; +import net.floodlightcontroller.core.util.AppCookie; import net.floodlightcontroller.debugcounter.IDebugCounterService; import net.floodlightcontroller.debugcounter.MockDebugCounterService; import net.floodlightcontroller.packet.ARP; @@ -43,10 +48,13 @@ import net.floodlightcontroller.packet.UDP; import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.restserver.RestApiServer; import net.floodlightcontroller.routing.IRoutingDecision; +import net.floodlightcontroller.routing.IRoutingService; import net.floodlightcontroller.storage.IStorageSourceService; import net.floodlightcontroller.storage.memory.MemoryStorageSource; import net.floodlightcontroller.test.FloodlightTestCase; +import org.easymock.Capture; +import org.easymock.CaptureType; import org.easymock.EasyMock; import org.junit.Before; import org.junit.Test; @@ -61,9 +69,11 @@ import org.projectfloodlight.openflow.types.IPv4Address; import org.projectfloodlight.openflow.types.IPv4AddressWithMask; import org.projectfloodlight.openflow.types.IpProtocol; import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.Masked; import org.projectfloodlight.openflow.types.OFBufferId; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.TransportPort; +import org.projectfloodlight.openflow.types.U64; /** * Unit test for stateless firewall implemented as a Google Summer of Code project. @@ -71,6 +81,7 @@ import org.projectfloodlight.openflow.types.TransportPort; * @author Amer Tahir */ public class FirewallTest extends FloodlightTestCase { + protected IRoutingService routingService; protected FloodlightContext cntx; protected OFPacketIn packetIn; protected IOFSwitch sw; @@ -83,6 +94,14 @@ public class FirewallTest extends FloodlightTestCase { private Firewall firewall; private MockDebugCounterService debugCounterService; public static String TestSwitch1DPID = "00:00:00:00:00:00:00:01"; + private static final short APP_ID = 30; + static { + AppCookie.registerApp(APP_ID, "Firewall"); + } + private static final U64 DENY_BCAST_COOKIE = AppCookie.makeCookie(APP_ID, 0xaaaaaaaa); + private static final U64 ALLOW_BCAST_COOKIE = AppCookie.makeCookie(APP_ID, 0x55555555); + private static final U64 RULE_MISS_COOKIE = AppCookie.makeCookie(APP_ID, -1); + @Override @Before @@ -95,6 +114,7 @@ public class FirewallTest extends FloodlightTestCase { firewall = new Firewall(); MemoryStorageSource storageService = new MemoryStorageSource(); RestApiServer restApi = new RestApiServer(); + routingService = createMock(IRoutingService.class); // Mock switches DatapathId dpid = DatapathId.of(TestSwitch1DPID); @@ -114,6 +134,7 @@ public class FirewallTest extends FloodlightTestCase { fmc.addService(IFirewallService.class, firewall); fmc.addService(IStorageSourceService.class, storageService); fmc.addService(IRestApiService.class, restApi); + fmc.addService(IRoutingService.class, routingService); debugCounterService.init(fmc); storageService.init(fmc); @@ -240,6 +261,32 @@ public class FirewallTest extends FloodlightTestCase { IFloodlightProviderService.CONTEXT_PI_PAYLOAD, (Ethernet)packet); } + + public boolean compareU64ListsOrdered(List<Masked<U64>> a,List<Masked<U64>> b){ + Object[] aArray = a.toArray(); + Object[] bArray = b.toArray(); + if (aArray.length != bArray.length) return false; + for(int i = 0; i<aArray.length; i++){ + if(aArray[i].equals(bArray[i]) == false){ + return false; + } + } + return true; + } + + @Test + public void enableFirewall() throws Exception{ + Capture<ArrayList<Masked<U64>>> wc1 = EasyMock.newCapture(CaptureType.ALL); + routingService.handleRoutingDecisionChange(capture(wc1)); + ArrayList<Masked<U64>> test_changes = new ArrayList<Masked<U64>>(); + + replay(routingService); + firewall.enableFirewall(true); + verify(routingService); + test_changes.add(Masked.of(Firewall.DEFAULT_COOKIE, AppCookie.getAppFieldMask())); + + assertTrue(compareU64ListsOrdered(wc1.getValue(),test_changes)); + } @Test public void testNoRules() throws Exception { @@ -255,10 +302,15 @@ public class FirewallTest extends FloodlightTestCase { IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); // no rules to match, so firewall should deny assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP); + assertEquals(RULE_MISS_COOKIE, decision.getDescriptor()); } @Test public void testReadRulesFromStorage() throws Exception { + Capture<ArrayList<Masked<U64>>> wc1 = EasyMock.newCapture(CaptureType.ALL); + routingService.handleRoutingDecisionChange(capture(wc1)); + ArrayList<Masked<U64>> test_changes = new ArrayList<Masked<U64>>(); + U64 singleRuleMask = AppCookie.getAppFieldMask().or(AppCookie.getUserFieldMask()); // add 2 rules first FirewallRule rule = new FirewallRule(); rule.in_port = OFPort.of(2); @@ -266,7 +318,17 @@ public class FirewallTest extends FloodlightTestCase { rule.dl_dst = MacAddress.of("00:00:00:00:00:02"); rule.priority = 1; rule.action = FirewallRule.FirewallAction.DROP; + replay(routingService); firewall.addRule(rule); + verify(routingService); + test_changes.add(Masked.of(AppCookie.makeCookie(APP_ID, rule.ruleid), singleRuleMask)); + test_changes.add(Masked.of(RULE_MISS_COOKIE, singleRuleMask)); + assertEquals(compareU64ListsOrdered(wc1.getValue(),test_changes),true); + reset(routingService); + // next rule + wc1 = EasyMock.newCapture(CaptureType.ALL); + test_changes = new ArrayList<Masked<U64>>(); + routingService.handleRoutingDecisionChange(capture(wc1)); rule = new FirewallRule(); rule.in_port = OFPort.of(3); rule.dl_src = MacAddress.of("00:00:00:00:00:02"); @@ -276,9 +338,16 @@ public class FirewallTest extends FloodlightTestCase { rule.tp_dst = TransportPort.of(80); rule.priority = 2; rule.action = FirewallRule.FirewallAction.ALLOW; + replay(routingService); firewall.addRule(rule); - + verify(routingService); + test_changes.add(Masked.of(AppCookie.makeCookie(APP_ID, rule.ruleid), singleRuleMask)); + test_changes.add(Masked.of(RULE_MISS_COOKIE, singleRuleMask)); + assertTrue(compareU64ListsOrdered(wc1.getValue(),test_changes)); + reset(routingService); + List<FirewallRule> rules = firewall.readRulesFromStorage(); + // verify rule 1 FirewallRule r = rules.get(0); assertEquals(r.in_port, OFPort.of(2)); @@ -321,15 +390,26 @@ public class FirewallTest extends FloodlightTestCase { rule.priority = 1; firewall.addRule(rule); int rid = rule.ruleid; + reset(routingService); + + Capture<ArrayList<Masked<U64>>> wc1 = EasyMock.newCapture(CaptureType.ALL); + routingService.handleRoutingDecisionChange(capture(wc1)); + ArrayList<Masked<U64>> test_changes = new ArrayList<Masked<U64>>(); + U64 singleRuleMask = AppCookie.getAppFieldMask().or(AppCookie.getUserFieldMask()); List<Map<String, Object>> rulesFromStorage = firewall.getStorageRules(); assertEquals(1, rulesFromStorage.size()); assertEquals(Integer.parseInt((String)rulesFromStorage.get(0).get("ruleid")), rid); // delete rule + replay(routingService); firewall.deleteRule(rid); + verify(routingService); + test_changes.add(Masked.of(AppCookie.makeCookie(APP_ID, rule.ruleid), singleRuleMask)); rulesFromStorage = firewall.getStorageRules(); assertEquals(0, rulesFromStorage.size()); + assertTrue(compareU64ListsOrdered(wc1.getValue(),test_changes)); + reset(routingService); } @Test @@ -373,6 +453,7 @@ public class FirewallTest extends FloodlightTestCase { rule.any_nw_dst = false; rule.priority = 1; firewall.addRule(rule); + U64 TCP_COOKIE = AppCookie.makeCookie(APP_ID, rule.ruleid); // simulate a packet-in events @@ -382,6 +463,7 @@ public class FirewallTest extends FloodlightTestCase { IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); assertEquals(IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD, decision.getRoutingAction()); + assertEquals(TCP_COOKIE, decision.getDescriptor()); // clear decision IRoutingDecision.rtStore.remove(cntx, IRoutingDecision.CONTEXT_DECISION); @@ -392,6 +474,7 @@ public class FirewallTest extends FloodlightTestCase { decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); assertEquals(IRoutingDecision.RoutingAction.DROP, decision.getRoutingAction()); + assertEquals(RULE_MISS_COOKIE, decision.getDescriptor()); } @Test @@ -407,12 +490,14 @@ public class FirewallTest extends FloodlightTestCase { rule.tp_dst = TransportPort.of(80); rule.priority = 1; firewall.addRule(rule); + U64 TCP_COOKIE = AppCookie.makeCookie(APP_ID, rule.ruleid); // add block all rule rule = new FirewallRule(); rule.action = FirewallRule.FirewallAction.DROP; rule.priority = 2; firewall.addRule(rule); + U64 BLOCK_ALL_COOKIE = AppCookie.makeCookie(APP_ID, rule.ruleid); assertEquals(2, firewall.rules.size()); @@ -424,6 +509,7 @@ public class FirewallTest extends FloodlightTestCase { IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD); + assertEquals(TCP_COOKIE, decision.getDescriptor()); // clear decision IRoutingDecision.rtStore.remove(cntx, IRoutingDecision.CONTEXT_DECISION); @@ -436,6 +522,7 @@ public class FirewallTest extends FloodlightTestCase { decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP); + assertEquals(BLOCK_ALL_COOKIE, decision.getDescriptor()); } @Test @@ -454,6 +541,7 @@ public class FirewallTest extends FloodlightTestCase { // broadcast-ARP traffic should be allowed IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); assertEquals(IRoutingDecision.RoutingAction.MULTICAST, decision.getRoutingAction()); + assertEquals(ALLOW_BCAST_COOKIE, decision.getDescriptor()); // clear decision IRoutingDecision.rtStore.remove(cntx, IRoutingDecision.CONTEXT_DECISION); @@ -467,6 +555,7 @@ public class FirewallTest extends FloodlightTestCase { // ARP reply traffic should be denied decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP); + assertEquals(RULE_MISS_COOKIE, decision.getDescriptor()); } @Test @@ -488,6 +577,7 @@ public class FirewallTest extends FloodlightTestCase { // broadcast traffic should be allowed IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); assertEquals(IRoutingDecision.RoutingAction.MULTICAST, decision.getRoutingAction()); + assertEquals(ALLOW_BCAST_COOKIE, decision.getDescriptor()); } @Test @@ -506,6 +596,7 @@ public class FirewallTest extends FloodlightTestCase { // malformed broadcast traffic should NOT be allowed IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.DROP); + assertEquals(DENY_BCAST_COOKIE, decision.getDescriptor()); } @Test @@ -521,6 +612,7 @@ public class FirewallTest extends FloodlightTestCase { rule.any_dl_dst = false; rule.priority = 1; firewall.addRule(rule); + U64 L2_LAYER_COOKIE = AppCookie.makeCookie(APP_ID, rule.ruleid); // add TCP deny all rule rule = new FirewallRule(); @@ -538,6 +630,7 @@ public class FirewallTest extends FloodlightTestCase { IRoutingDecision decision = IRoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); assertEquals(decision.getRoutingAction(), IRoutingDecision.RoutingAction.FORWARD_OR_FLOOD); + assertEquals(L2_LAYER_COOKIE, decision.getDescriptor()); } @Test @@ -584,5 +677,17 @@ public class FirewallTest extends FloodlightTestCase { // Change dl_dst, rules no longer "same" rule2.dl_dst = MacAddress.of("00:01:02:03:04:05"); assertFalse(rule1.isSameAs(rule2)); + + } + + /* Testing to make sure that the cookies are properly formatted with the correct info before hitting the firewall. */ + @Test + public void cookieAddedSuccessfully() { + assertEquals("DENY_BCAST_COOKIE app_id is not correct", APP_ID, AppCookie.extractApp(DENY_BCAST_COOKIE)); + assertEquals("DENY_BCAST_COOKIE user_id is not correct", 0xaaaaaaaa, AppCookie.extractUser(DENY_BCAST_COOKIE)); + assertEquals("ALLOW_BCAST_COOKIE app_id is not correct", APP_ID, AppCookie.extractApp(DENY_BCAST_COOKIE)); + assertEquals("ALLOW_BCAST_COOKIE user_id is not correct", 0x55555555, AppCookie.extractUser(ALLOW_BCAST_COOKIE)); + assertEquals("RULE_MISS_COOKIE app_id is not correct", APP_ID, AppCookie.extractApp(DENY_BCAST_COOKIE)); + assertEquals("RULE_MISS_COOKIE user_id is not correct", -1, AppCookie.extractUser(RULE_MISS_COOKIE)); } } diff --git a/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java b/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java index 9d68ba9f98a4309d13fda155978e799f12b083db..beaf22ad490c5adaffb2ed63fcc97e5781153cba 100644 --- a/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java +++ b/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -36,6 +37,7 @@ import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.test.MockThreadPoolService; import net.floodlightcontroller.core.types.NodePortTuple; +import net.floodlightcontroller.core.util.AppCookie; import net.floodlightcontroller.debugcounter.IDebugCounterService; import net.floodlightcontroller.debugcounter.MockDebugCounterService; import net.floodlightcontroller.devicemanager.internal.DefaultEntityClassifier; @@ -51,8 +53,10 @@ import net.floodlightcontroller.packet.IPacket; import net.floodlightcontroller.packet.IPv4; import net.floodlightcontroller.packet.IPv6; import net.floodlightcontroller.packet.UDP; +import net.floodlightcontroller.routing.IRoutingDecision.RoutingAction; import net.floodlightcontroller.routing.IRoutingService; import net.floodlightcontroller.routing.Route; +import net.floodlightcontroller.routing.RoutingDecision; import net.floodlightcontroller.test.FloodlightTestCase; import net.floodlightcontroller.threadpool.IThreadPoolService; import net.floodlightcontroller.topology.ITopologyListener; @@ -81,6 +85,7 @@ import org.projectfloodlight.openflow.types.IPv4Address; import org.projectfloodlight.openflow.types.IPv6Address; import org.projectfloodlight.openflow.types.IpProtocol; import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.Masked; import org.projectfloodlight.openflow.types.OFBufferId; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.TransportPort; @@ -92,6 +97,8 @@ import org.projectfloodlight.openflow.protocol.action.OFActionOutput; import org.sdnplatform.sync.ISyncService; import org.sdnplatform.sync.test.MockSyncService; +import com.google.common.collect.ImmutableList; + public class ForwardingTest extends FloodlightTestCase { protected FloodlightContext cntx; protected MockDeviceManager deviceManager; @@ -176,6 +183,7 @@ public class ForwardingTest extends FloodlightTestCase { expect(sw1.getId()).andReturn(DatapathId.of(1L)).anyTimes(); expect(sw1.getOFFactory()).andReturn(factory).anyTimes(); expect(sw1.getBuffers()).andReturn(swFeatures.getNBuffers()).anyTimes(); + sw2 = EasyMock.createMock(IOFSwitch.class); expect(sw2.getId()).andReturn(DatapathId.of(2L)).anyTimes(); @@ -316,6 +324,36 @@ public class ForwardingTest extends FloodlightTestCase { IDeviceService.CONTEXT_DST_DEVICE); } + static boolean messageListsEqualIgnoreXid(List<OFMessage> c1, List<OFMessage> c2) { + if (c1 == c2) { + return true; + } + + if (c1 == null || c2 == null) { + return false; + } + + if (c1.size() != c2.size()) { + return false; + } + + Iterator<OFMessage> it1 = c1.iterator(); + Iterator<OFMessage> it2 = c2.iterator(); + while (it1.hasNext()) { + OFMessage m1 = it1.next(); + OFMessage m2 = it2.next(); + if (m1 == m2) { + continue; + } + + if (m1 == null || m2 == null || !m1.equalsIgnoreXid(m2)) { + return false; + } + } + + return true; + } + enum DestDeviceToLearn { NONE, DEVICE1 ,DEVICE2 }; public void learnDevices(DestDeviceToLearn destDeviceToLearn) { // Build src and dest devices @@ -445,8 +483,9 @@ public class ForwardingTest extends FloodlightTestCase { nptList.add(new NodePortTuple(DatapathId.of(2L), OFPort.of(1))); nptList.add(new NodePortTuple(DatapathId.of(2L), OFPort.of(3))); route.setPath(nptList); - expect(routingEngine.getRoute(DatapathId.of(1L), OFPort.of(1), DatapathId.of(2L), OFPort.of(3), U64.ZERO)).andReturn(route).atLeastOnce(); - + reset(routingEngine); + expect(routingEngine.getRoute(DatapathId.of(1L), OFPort.of(1), DatapathId.of(2L), OFPort.of(3), Forwarding.DEFAULT_FORWARDING_COOKIE)).andReturn(route).atLeastOnce(); + // Expected Flow-mods Match match = packetIn.getMatch(); OFActionOutput action = factory.actions().output(OFPort.of(3), Integer.MAX_VALUE); @@ -515,8 +554,9 @@ public class ForwardingTest extends FloodlightTestCase { nptList.add(new NodePortTuple(DatapathId.of(2L), OFPort.of(1))); nptList.add(new NodePortTuple(DatapathId.of(2L), OFPort.of(3))); route.setPath(nptList); - expect(routingEngine.getRoute(DatapathId.of(1L), OFPort.of(1), DatapathId.of(2L), OFPort.of(3), U64.ZERO)).andReturn(route).atLeastOnce(); - + reset(routingEngine); + expect(routingEngine.getRoute(DatapathId.of(1L), OFPort.of(1), DatapathId.of(2L), OFPort.of(3), Forwarding.DEFAULT_FORWARDING_COOKIE)).andReturn(route).atLeastOnce(); + // Expected Flow-mods Match match = packetInIPv6.getMatch(); OFActionOutput action = factory.actions().output(OFPort.of(3), Integer.MAX_VALUE); @@ -581,13 +621,14 @@ public class ForwardingTest extends FloodlightTestCase { Route route = new Route(DatapathId.of(1L), DatapathId.of(1L)); route.getPath().add(new NodePortTuple(DatapathId.of(1L), OFPort.of(1))); route.getPath().add(new NodePortTuple(DatapathId.of(1L), OFPort.of(3))); - expect(routingEngine.getRoute(DatapathId.of(1L), OFPort.of(1), DatapathId.of(1L), OFPort.of(3), U64.ZERO)).andReturn(route).atLeastOnce(); - + // Expected Flow-mods Match match = packetIn.getMatch(); OFActionOutput action = factory.actions().output(OFPort.of(3), Integer.MAX_VALUE); List<OFAction> actions = new ArrayList<OFAction>(); actions.add(action); + + //routingEngine.addRoutingDecisionChangedListener(anyObject(IRoutingDecisionChangedListener.class)); OFFlowMod fm1 = factory.buildFlowAdd() .setIdleTimeout((short)5) @@ -612,6 +653,8 @@ public class ForwardingTest extends FloodlightTestCase { expect(topology.isEdge(DatapathId.of(1L), OFPort.of(3))).andReturn(true).anyTimes(); // Reset mocks, trigger the packet in, and validate results + reset(routingEngine); + expect(routingEngine.getRoute(DatapathId.of(1L), OFPort.of(1), DatapathId.of(1L), OFPort.of(3), Forwarding.DEFAULT_FORWARDING_COOKIE)).andReturn(route).atLeastOnce(); replay(sw1, sw2, routingEngine, topology); forwarding.receive(sw1, this.packetIn, cntx); verify(sw1, sw2, routingEngine); @@ -635,8 +678,9 @@ public class ForwardingTest extends FloodlightTestCase { Route route = new Route(DatapathId.of(1L), DatapathId.of(1L)); route.getPath().add(new NodePortTuple(DatapathId.of(1L), OFPort.of(1))); route.getPath().add(new NodePortTuple(DatapathId.of(1L), OFPort.of(3))); - expect(routingEngine.getRoute(DatapathId.of(1L), OFPort.of(1), DatapathId.of(1L), OFPort.of(3), U64.ZERO)).andReturn(route).atLeastOnce(); - + reset(routingEngine); + expect(routingEngine.getRoute(DatapathId.of(1L), OFPort.of(1), DatapathId.of(1L), OFPort.of(3), Forwarding.DEFAULT_FORWARDING_COOKIE)).andReturn(route).atLeastOnce(); + // Expected Flow-mods Match match = packetInIPv6.getMatch(); OFActionOutput action = factory.actions().output(OFPort.of(3), Integer.MAX_VALUE); @@ -743,6 +787,7 @@ public class ForwardingTest extends FloodlightTestCase { // Set no destination attachment point or route // expect no Flow-mod but expect the packet to be flooded + reset(routingEngine); Capture<OFMessage> wc1 = EasyMock.newCapture(CaptureType.ALL); @@ -771,6 +816,8 @@ public class ForwardingTest extends FloodlightTestCase { @Test public void testForwardNoPathIPv6() throws Exception { learnDevicesIPv6(DestDeviceToLearn.NONE); + + reset(routingEngine); // Set no destination attachment point or route // expect no Flow-mod but expect the packet to be flooded @@ -800,4 +847,230 @@ public class ForwardingTest extends FloodlightTestCase { removeDeviceFromContext(); } + + /* + * TODO Consider adding test cases for other Decision != null paths (I only added FORWARD and none of the paths had test cases) + */ + @Test + public void testForwardDecisionForwardingCookieZero() throws Exception { + learnDevices(DestDeviceToLearn.DEVICE2); + + Capture<OFMessage> wc1 = EasyMock.newCapture(CaptureType.ALL); + Capture<OFMessage> wc2 = EasyMock.newCapture(CaptureType.ALL); + + Route route = new Route(DatapathId.of(1L), DatapathId.of(1L)); + route.getPath().add(new NodePortTuple(DatapathId.of(1L), OFPort.of(1))); + route.getPath().add(new NodePortTuple(DatapathId.of(1L), OFPort.of(3))); + reset(routingEngine); + expect(routingEngine.getRoute(DatapathId.of(1L), OFPort.of(1), DatapathId.of(1L), OFPort.of(3), Forwarding.DEFAULT_FORWARDING_COOKIE)).andReturn(route).atLeastOnce(); + + // Expected Flow-mods + Match match = packetIn.getMatch(); + OFActionOutput action = factory.actions().output(OFPort.of(3), Integer.MAX_VALUE); + List<OFAction> actions = new ArrayList<OFAction>(); + actions.add(action); + + RoutingDecision decision = new RoutingDecision(DatapathId.of(1L), OFPort.of(1), dstDevice1, RoutingAction.FORWARD); + decision.setDescriptor(U64.ZERO); + decision.addToContext(cntx); + + OFFlowMod fm1 = factory.buildFlowAdd() + .setIdleTimeout((short)5) + .setMatch(match) + .setActions(actions) + .setOutPort(OFPort.of(3)) + .setBufferId(OFBufferId.NO_BUFFER) + .setCookie(U64.of(2L<< 52)) + .setPriority(1) + .build(); + + // Record expected packet-outs/flow-mods + expect(sw1.write(capture(wc1))).andReturn(true).once(); + expect(sw1.write(capture(wc2))).andReturn(true).once(); + + reset(topology); + expect(topology.isIncomingBroadcastAllowed(DatapathId.of(anyLong()), OFPort.of(anyShort()))).andReturn(true).anyTimes(); + expect(topology.getOpenflowDomainId(DatapathId.of(1L))).andReturn(DatapathId.of(1L)).anyTimes(); + expect(topology.isAttachmentPointPort(DatapathId.of(1L), OFPort.of(1))).andReturn(true).anyTimes(); + expect(topology.isAttachmentPointPort(DatapathId.of(1L), OFPort.of(3))).andReturn(true).anyTimes(); + expect(topology.isEdge(DatapathId.of(1L), OFPort.of(1))).andReturn(true).anyTimes(); + expect(topology.isEdge(DatapathId.of(1L), OFPort.of(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); + + assertTrue(wc1.hasCaptured()); + assertTrue(wc2.hasCaptured()); + + assertTrue(OFMessageUtils.equalsIgnoreXid(wc1.getValue(), fm1)); + assertTrue(OFMessageUtils.equalsIgnoreXid(wc2.getValue(), packetOut)); + } + + @Test + public void testForwardDecisionForwardingCookieNotZero() throws Exception { + learnDevices(DestDeviceToLearn.DEVICE2); + + Capture<OFMessage> wc1 = EasyMock.newCapture(CaptureType.ALL); + Capture<OFMessage> wc2 = EasyMock.newCapture(CaptureType.ALL); + + Route route = new Route(DatapathId.of(1L), DatapathId.of(1L)); + route.getPath().add(new NodePortTuple(DatapathId.of(1L), OFPort.of(1))); + route.getPath().add(new NodePortTuple(DatapathId.of(1L), OFPort.of(3))); + reset(routingEngine); + expect(routingEngine.getRoute(DatapathId.of(1L), OFPort.of(1), DatapathId.of(1L), OFPort.of(3), U64.of(0x200000FFffFFffL))).andReturn(route).atLeastOnce(); + + // Expected Flow-mods + Match match = packetIn.getMatch(); + OFActionOutput action = factory.actions().output(OFPort.of(3), Integer.MAX_VALUE); + List<OFAction> actions = new ArrayList<OFAction>(); + actions.add(action); + + RoutingDecision decision = new RoutingDecision(DatapathId.of(1L), OFPort.of(1), dstDevice1, RoutingAction.FORWARD); + decision.setDescriptor(U64.of(0x00000000ffffffffL)); + decision.addToContext(cntx); + //RoutingDecision de2 = (RoutingDecision) RoutingDecision.rtStore.get(cntx, IRoutingDecision.CONTEXT_DECISION); // Same as decision + //(DatapathId swDipd, OFPort inPort, IDevice srcDevice, RoutingAction action); + + OFFlowMod fm1 = factory.buildFlowAdd() + .setIdleTimeout((short)5) + .setMatch(match) + .setActions(actions) + .setOutPort(OFPort.of(3)) + .setBufferId(OFBufferId.NO_BUFFER) + .setCookie(U64.of(2L<< 52 | 0xFFffFFffL)) + .setPriority(1) + .build(); + + // Record expected packet-outs/flow-mods + expect(sw1.write(capture(wc1))).andReturn(true).once(); + expect(sw1.write(capture(wc2))).andReturn(true).once(); + + reset(topology); + expect(topology.isIncomingBroadcastAllowed(DatapathId.of(anyLong()), OFPort.of(anyShort()))).andReturn(true).anyTimes(); + expect(topology.getOpenflowDomainId(DatapathId.of(1L))).andReturn(DatapathId.of(1L)).anyTimes(); + expect(topology.isAttachmentPointPort(DatapathId.of(1L), OFPort.of(1))).andReturn(true).anyTimes(); + expect(topology.isAttachmentPointPort(DatapathId.of(1L), OFPort.of(3))).andReturn(true).anyTimes(); + expect(topology.isEdge(DatapathId.of(1L), OFPort.of(1))).andReturn(true).anyTimes(); + expect(topology.isEdge(DatapathId.of(1L), OFPort.of(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); + + assertTrue(wc1.hasCaptured()); + assertTrue(wc2.hasCaptured()); + + assertTrue(OFMessageUtils.equalsIgnoreXid(wc1.getValue(), fm1)); + assertTrue(OFMessageUtils.equalsIgnoreXid(wc2.getValue(), packetOut)); + } + + @Test + public void testForwardDeleteFlowsByDescriptorSingle() throws Exception { + reset(routingEngine); + + Capture<List<OFMessage>> wc1 = EasyMock.newCapture(CaptureType.ALL); + Capture<List<OFMessage>> wc2 = EasyMock.newCapture(CaptureType.ALL); + + List<Masked<U64>> descriptors = new ArrayList<Masked<U64>>(); + descriptors.add(Masked.of( + U64.of(0x00000000FFffFFffL), + U64.of(0x00200000FFffFFffL))); // User mask = 0xffFFffFFL which is forwarding.DECISION_MASK/AppCookie.USER_MASK//descriptors.add(Masked.of(U64.of(0x00000000FFffFFffL),U64.of(0x0020000000000000L)));//descriptors.add(Masked.of(U64.of(0xffFFffFFffFFffFFL),U64.of(0x00200000FFffFFffL))); // Mask = 0xffFFffFFffFFffFFL which is the value returned by forwarding.AppCookie.getAppFieldMask()//descriptors.add(Masked.of(U64.of(0xffFFffFFffFFffFFL),U64.of(0x0020000000000000L))); + + expect(sw1.getStatus()).andReturn(IOFSwitch.SwitchStatus.MASTER).anyTimes(); + expect(sw2.getStatus()).andReturn(IOFSwitch.SwitchStatus.MASTER).anyTimes(); + + expect(sw1.write(capture(wc1))).andReturn(ImmutableList.of()).once(); + expect(sw2.write(capture(wc2))).andReturn(ImmutableList.of()).once(); + + replay(sw1, sw2, routingEngine); + forwarding.deleteFlowsByDescriptor(descriptors); + verify(sw1, sw2, routingEngine); + + assertTrue(wc1.hasCaptured()); + assertTrue(wc2.hasCaptured()); + + Masked<U64> masked_cookie = Masked.of( + AppCookie.makeCookie(Forwarding.FORWARDING_APP_ID, (int)4294967295L), + AppCookie.getAppFieldMask().or(U64.of(0xffffffffL))); + List<OFMessage> msgs_test = new ArrayList<>(); + msgs_test.add( factory.buildFlowDelete() + .setCookie(masked_cookie.getValue()) + .setCookieMask(masked_cookie.getMask()) + .build()); + + assertTrue(messageListsEqualIgnoreXid(wc1.getValue(), msgs_test)); + assertTrue(messageListsEqualIgnoreXid(wc2.getValue(), msgs_test)); + } + + @Test + public void testForwardDeleteFlowsByDescriptorMultiple() throws Exception { + reset(routingEngine); + + Capture<List<OFMessage>> wc1 = EasyMock.newCapture(CaptureType.ALL); + Capture<List<OFMessage>> wc2 = EasyMock.newCapture(CaptureType.ALL); + + List<Masked<U64>> descriptors = new ArrayList<Masked<U64>>(); + descriptors.add(Masked.of( + U64.of(0x00000000FFffFFffL), + U64.of(0x00200000FFffFFffL))); // User mask = 0xffFFffFFL which is forwarding.DECISION_MASK/AppCookie.USER_MASK + descriptors.add(Masked.of( + U64.of(0x00000000FFffFFffL), + U64.of(0x0020000000000000L))); + + expect(sw1.getStatus()).andReturn(IOFSwitch.SwitchStatus.MASTER).anyTimes(); + expect(sw2.getStatus()).andReturn(IOFSwitch.SwitchStatus.MASTER).anyTimes(); + + expect(sw1.write(capture(wc1))).andReturn(ImmutableList.of()).once(); + expect(sw2.write(capture(wc2))).andReturn(ImmutableList.of()).once(); + + replay(sw1, sw2, routingEngine); + forwarding.deleteFlowsByDescriptor(descriptors); + verify(sw1, sw2, routingEngine); + + assertTrue(wc1.hasCaptured()); + assertTrue(wc2.hasCaptured()); + + // Cookies + Masked<U64> masked_cookie = Masked.of( AppCookie.makeCookie(Forwarding.FORWARDING_APP_ID, (int)4294967295L), + AppCookie.getAppFieldMask().or(U64.of(0xffffffffL))); + Masked<U64> masked_cookie2 = Masked.of( AppCookie.makeCookie(Forwarding.FORWARDING_APP_ID, 0), + AppCookie.getAppFieldMask().or(U64.of(0x0L))); + // Add cookies to a msg set + List<OFMessage> msgs_test = new ArrayList<OFMessage>(); + msgs_test.add( factory.buildFlowDelete() + .setCookie(masked_cookie.getValue()) + .setCookieMask(masked_cookie.getMask()) + .build()); + msgs_test.add( factory.buildFlowDelete() + .setCookie(masked_cookie2.getValue()) + .setCookieMask(masked_cookie2.getMask()) + .build()); + assertTrue(messageListsEqualIgnoreXid(wc1.getValue(), msgs_test)); + assertTrue(messageListsEqualIgnoreXid(wc2.getValue(), msgs_test)); + } + + @Test + public void testForwardDeleteFlowsByDescriptorNoCookies() throws Exception { + reset(routingEngine); + + List<Masked<U64>> descriptors = new ArrayList<Masked<U64>>(); + + replay(routingEngine); + forwarding.deleteFlowsByDescriptor(descriptors); + verify(routingEngine); + } + + @Test + public void testForwardDeleteFlowsByDescriptorNoCookiesContainer() throws Exception { + reset(routingEngine); + + List<Masked<U64>> descriptors = null; + + replay(routingEngine); + forwarding.deleteFlowsByDescriptor(descriptors); + verify(routingEngine); + } } \ No newline at end of file