diff --git a/src/main/java/net/floodlightcontroller/core/util/AppCookie.java b/src/main/java/net/floodlightcontroller/core/util/AppCookie.java index d35e6a9e56631e21f3c13f52db9b6f693199fe76..da7997570be5cef9d438c9efae43f6c311575a74 100644 --- a/src/main/java/net/floodlightcontroller/core/util/AppCookie.java +++ b/src/main/java/net/floodlightcontroller/core/util/AppCookie.java @@ -36,48 +36,37 @@ import org.projectfloodlight.openflow.types.U64; * * The 64 bit OpenFlow cookie field used in the following way * <li> Bit 63 -- 52 (12 bit): the AppId - * <li> Bit 51 -- 32 (20 bit): currently unused. Set to 0. - * <li> Bit 31 -- 0 (32 bit): user data + * <li> Bit 51 -- 0 (52 bit): user data * * FIXME: The class should be a singleton. The registration method should * return an instance of class. This instance should then be used to generate * flow cookies. Ideally, we would also represent a flow cookie as a class * instance. * - * * @author capveg - * */ public class AppCookie { - static final int APP_ID_BITS = 12; - static final long APP_ID_MASK = (1L << APP_ID_BITS) - 1; - static final int APP_ID_SHIFT = (64 - APP_ID_BITS); - - static final long USER_MASK = 0x00000000FFFFFFFFL; + private static final int APP_ID_BITS = 12; + private static final long APP_ID_MASK = (1L << APP_ID_BITS) - 1; + private static final int APP_ID_SHIFT = 64 - APP_ID_BITS; - /**the following bit will be set accordingly if the field is rewritten by application. e.g. VRS or floating IP - * FIXME: these should not be in AppCookie and they shoul not use - * the reserved bit range*/ - static final int SRC_MAC_REWRITE_BIT=33; - static final int DEST_MAC_REWRITE_BIT=34; - static final int SRC_IP_REWRITE_BIT=35; - static final int DEST_IP_REWRITE_BIT=36; + private static final long USER_MASK = 0x000FFFFFFFFFFFFFL; - - static final long REWRITE_MASK= 0x000f00000000L; - private static ConcurrentMap<Integer, String> appIdMap = - new ConcurrentHashMap<Integer, String>(); + private static ConcurrentMap<Long, String> appIdMap = + new ConcurrentHashMap<Long, String>(); /** - * Returns a mask suitable for matching the App ID within a cookie. + * Returns a mask suitable for matching the app ID within a cookie. + * @return a mask representing the bits used for the app ID in the 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. + * Returns a mask suitable for matching the user field within a cookie. + * @return a mask representing the bits used for user data in the cookie */ static public U64 getUserFieldMask() { return U64.of(USER_MASK); @@ -92,76 +81,43 @@ public class AppCookie { * @throws IllegalStateException if the application has not been registered */ - static public U64 makeCookie(int application, int user) { + static public U64 makeCookie(long application, long user) { if (!appIdMap.containsKey(application)) { throw new AppIDNotRegisteredException(application); } - long longApp = application; - long longUser = user & USER_MASK; // mask to prevent sign extend - return U64.of((longApp << APP_ID_SHIFT) | longUser); + user = user & USER_MASK; // mask to prevent sign extend + return U64.of((application << APP_ID_SHIFT) | user); } /** - * Extract the application id from a flow cookie. Does <em>not</em> check - * whether the application id is registered + * Extract the application id from a flow cookie. Does <em>not</em> + * check whether the application id is registered. The app ID is + * defined by the {@link #getAppFieldMask()} bits * @param cookie * @return */ - static public int extractApp(U64 cookie) { - return (int)((cookie.getValue() >>> APP_ID_SHIFT) & APP_ID_MASK); + static public long extractApp(U64 cookie) { + return (cookie.getValue() >>> APP_ID_SHIFT) & APP_ID_MASK; } - static public int extractUser(U64 cookie) { - return (int)(cookie.getValue() & USER_MASK); + /** + * Extract the user portion from a flow cookie, defined + * by the {@link #getUserFieldMask()} bits + * @param cookie + * @return + */ + static public long extractUser(U64 cookie) { + return cookie.getValue() & USER_MASK; } - static public boolean isRewriteFlagSet(U64 cookie) { - if ((cookie.getValue() & REWRITE_MASK) !=0L) - return true; - return false; - } - static public boolean isSrcMacRewriteFlagSet(U64 cookie) { - if ((cookie.getValue() & (1L << (SRC_MAC_REWRITE_BIT-1))) !=0L) - return true; - return false; - } - static public boolean isDestMacRewriteFlagSet(U64 cookie) { - if ((cookie.getValue() & (1L << (DEST_MAC_REWRITE_BIT-1))) !=0L) - return true; - return false; - } - static public boolean isSrcIpRewriteFlagSet(U64 cookie) { - if ((cookie.getValue() & (1L << (SRC_IP_REWRITE_BIT-1))) !=0L) - return true; - return false; - } - static public boolean isDestIpRewriteFlagSet(U64 cookie) { - if ((cookie.getValue() & (1L << (DEST_IP_REWRITE_BIT-1))) !=0L) - return true; - return false; - } - static public U64 setSrcMacRewriteFlag(U64 cookie) { - return U64.of(cookie.getValue() | (1L << (SRC_MAC_REWRITE_BIT-1))); - } - static public U64 setDestMacRewriteFlag(U64 cookie) { - return U64.of(cookie.getValue() | (1L << (DEST_MAC_REWRITE_BIT-1))); - } - static public U64 setSrcIpRewriteFlag(U64 cookie) { - return U64.of(cookie.getValue() | (1L << (SRC_IP_REWRITE_BIT-1))); - } - static public U64 setDestIpRewriteFlag(U64 cookie) { - return U64.of(cookie.getValue() | (1L << (DEST_IP_REWRITE_BIT-1))); - } /** * A lame attempt to prevent duplicate application ID. - * TODO: We should expose appID->appName map - * via REST API so CLI doesn't need a separate copy of the map. * * @param application * @param appName * @throws AppIDInUseException */ - public static void registerApp(int application, String appName) + public static void registerApp(long application, String appName) throws AppIDException { if ((application & APP_ID_MASK) != application) { @@ -179,7 +135,7 @@ public class AppCookie { * @param application * @return */ - public static String getAppName(int application) { + public static String getAppName(long application) { return appIdMap.get(application); } } diff --git a/src/main/java/net/floodlightcontroller/core/util/AppIDInUseException.java b/src/main/java/net/floodlightcontroller/core/util/AppIDInUseException.java index 077def6a3b91ef59d37450173958360f23f31533..073fa941b74dee76dba8fb22c9935fea1fe15560 100644 --- a/src/main/java/net/floodlightcontroller/core/util/AppIDInUseException.java +++ b/src/main/java/net/floodlightcontroller/core/util/AppIDInUseException.java @@ -3,9 +3,9 @@ package net.floodlightcontroller.core.util; public class AppIDInUseException extends AppIDException { private static final long serialVersionUID = 3167241821651094997L; - public AppIDInUseException(int appId, String oldAppName, + public AppIDInUseException(long application, String oldAppName, String newAppName) { - super(String.format("Tried to register application IdD %s for %s, but" + - "already registered for %s.", appId, oldAppName, newAppName)); + super(String.format("Tried to register application ID %s for %s, but" + + "already registered for %s.", application, oldAppName, newAppName)); } } \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/util/AppIDNotRegisteredException.java b/src/main/java/net/floodlightcontroller/core/util/AppIDNotRegisteredException.java index 4dab9610137f52e9e75639442dacc103d99ed19e..b53638d264c00f56775694de6d4e6bd14053f73d 100644 --- a/src/main/java/net/floodlightcontroller/core/util/AppIDNotRegisteredException.java +++ b/src/main/java/net/floodlightcontroller/core/util/AppIDNotRegisteredException.java @@ -3,7 +3,7 @@ package net.floodlightcontroller.core.util; public class AppIDNotRegisteredException extends AppIDException { private static final long serialVersionUID = -9195312786292237763L; - public AppIDNotRegisteredException(int application) { + public AppIDNotRegisteredException(long application) { super("Application Id " + application + " has not been registered"); } diff --git a/src/main/java/net/floodlightcontroller/firewall/Firewall.java b/src/main/java/net/floodlightcontroller/firewall/Firewall.java index 4ecc605c40e359ea828e3d380d2b18b9cc1886b6..1b2f2c6076b30f5e6824b39b33aa47552378a54d 100644 --- a/src/main/java/net/floodlightcontroller/firewall/Firewall.java +++ b/src/main/java/net/floodlightcontroller/firewall/Firewall.java @@ -66,6 +66,7 @@ import net.floodlightcontroller.routing.RoutingDecision; import net.floodlightcontroller.storage.IResultSet; import net.floodlightcontroller.storage.IStorageSourceService; import net.floodlightcontroller.storage.StorageException; +import net.floodlightcontroller.util.OFMessageUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,10 +87,10 @@ IFloodlightModule { 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); + private static final U64 DENY_BCAST_COOKIE = AppCookie.makeCookie(APP_ID, 0xaaaaaaL); + private static final U64 ALLOW_BCAST_COOKIE = AppCookie.makeCookie(APP_ID, 0x555555L); + private static final U64 RULE_MISS_COOKIE = AppCookie.makeCookie(APP_ID, 0xffffffL); + static final U64 DEFAULT_COOKIE = AppCookie.makeCookie(APP_ID, 0xffffffL); // service modules needed protected IFloodlightProviderService floodlightProvider; @@ -592,7 +593,7 @@ IFloodlightModule { public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) { Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD); - OFPort inPort = (pi.getVersion().compareTo(OFVersion.OF_12) < 0 ? pi.getInPort() : pi.getMatch().get(MatchField.IN_PORT)); + OFPort inPort = OFMessageUtils.getInPort(pi); // Allowing L2 broadcast + ARP broadcast request (also deny malformed // broadcasts -> L2 broadcast + L3 unicast) diff --git a/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java b/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java index 5cb01303b1ab62e8b832c9f7e96cc46616ed1714..71b38ee63ab4daed59f1e916034718a8da963cd4 100644 --- a/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java +++ b/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java @@ -21,9 +21,12 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; @@ -92,11 +95,132 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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); + protected static final Logger log = LoggerFactory.getLogger(Forwarding.class); - // 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. + /* + * Cookies are 64 bits: + * Example: 0x0123456789ABCDEF + * App ID: 0xFFF0000000000000 + * User: 0x000FFFFFFFFFFFFF + * + * Of the user portion, we further subdivide into routing decision + * bits and flowset bits. The former relates the flow to routing + * decisions, such as firewall allow or deny/drop. It allows for + * modification of the flows upon a future change in the routing + * decision. The latter indicates a "family" of flows or "flowset" + * used to complete an end-to-end connection between two devices + * or hosts in the network. It is used to assist in the entire + * flowset removal upon a link or port down event anywhere along + * the path. This is required in order to allow a new path to be + * used and a new flowset installed. + * + * TODO: shrink these masks if you need to add more subfields + * or need to allow for a larger number of routing decisions + * or flowsets + */ + + private static final short DECISION_BITS = 24; + private static final short DECISION_SHIFT = 0; + private static final long DECISION_MASK = ((1L << DECISION_BITS) - 1) << DECISION_SHIFT; + + private static final short FLOWSET_BITS = 28; + protected static final short FLOWSET_SHIFT = DECISION_BITS; + private static final long FLOWSET_MASK = ((1L << FLOWSET_BITS) - 1) << FLOWSET_SHIFT; + private static final long FLOWSET_MAX = (long) (Math.pow(2, FLOWSET_BITS) - 1); + protected static FlowSetIdRegistry flowSetIdRegistry; + + protected static class FlowSetIdRegistry { + private volatile Map<NodePortTuple, Set<U64>> nptToFlowSetIds; + private volatile Map<U64, Set<NodePortTuple>> flowSetIdToNpts; + + private volatile long flowSetGenerator = -1; + + private static volatile FlowSetIdRegistry instance; + + private FlowSetIdRegistry() { + nptToFlowSetIds = new ConcurrentHashMap<NodePortTuple, Set<U64>>(); + flowSetIdToNpts = new ConcurrentHashMap<U64, Set<NodePortTuple>>(); + } + + protected static FlowSetIdRegistry getInstance() { + if (instance == null) { + instance = new FlowSetIdRegistry(); + } + return instance; + } + + /** + * Only for use by unit test to help w/ordering + * @param seed + */ + protected void seedFlowSetIdForUnitTest(int seed) { + flowSetGenerator = seed; + } + + protected synchronized U64 generateFlowSetId() { + flowSetGenerator += 1; + if (flowSetGenerator == FLOWSET_MAX) { + flowSetGenerator = 0; + log.warn("Flowset IDs have exceeded capacity of {}. Flowset ID generator resetting back to 0", FLOWSET_MAX); + } + U64 id = U64.of(flowSetGenerator << FLOWSET_SHIFT); + log.debug("Generating flowset ID {}, shifted {}", flowSetGenerator, id); + return id; + } + + private void registerFlowSetId(NodePortTuple npt, U64 flowSetId) { + if (nptToFlowSetIds.containsKey(npt)) { + Set<U64> ids = nptToFlowSetIds.get(npt); + ids.add(flowSetId); + } else { + Set<U64> ids = new HashSet<U64>(); + ids.add(flowSetId); + nptToFlowSetIds.put(npt, ids); + } + + if (flowSetIdToNpts.containsKey(flowSetId)) { + Set<NodePortTuple> npts = flowSetIdToNpts.get(flowSetId); + npts.add(npt); + } else { + Set<NodePortTuple> npts = new HashSet<NodePortTuple>(); + npts.add(npt); + flowSetIdToNpts.put(flowSetId, npts); + } + } + + private Set<U64> getFlowSetIds(NodePortTuple npt) { + return nptToFlowSetIds.get(npt); + } + + private Set<NodePortTuple> getNodePortTuples(U64 flowSetId) { + return flowSetIdToNpts.get(flowSetId); + } + + private void removeNodePortTuple(NodePortTuple npt) { + nptToFlowSetIds.remove(npt); + + Iterator<Set<NodePortTuple>> itr = flowSetIdToNpts.values().iterator(); + while (itr.hasNext()) { + Set<NodePortTuple> npts = itr.next(); + npts.remove(npt); + } + } + + private void removeExpiredFlowSetId(U64 flowSetId, NodePortTuple avoid, Iterator<U64> avoidItr) { + flowSetIdToNpts.remove(flowSetId); + + Iterator<Entry<NodePortTuple, Set<U64>>> itr = nptToFlowSetIds.entrySet().iterator(); + while (itr.hasNext()) { + Entry<NodePortTuple, Set<U64>> e = itr.next(); + if (e.getKey().equals(avoid)) { + avoidItr.remove(); + } else { + Set<U64> ids = e.getValue(); + ids.remove(flowSetId); + } + } + } + } @Override public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) { @@ -145,17 +269,21 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF * 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 + * @return A cookie with our app id and the required fields masked-in */ - protected U64 makeForwardingCookie(IRoutingDecision decision) { - int user_fields = 0; + protected U64 makeForwardingCookie(IRoutingDecision decision, U64 flowSetId) { + long 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 (flowSetId != null) { + user_fields |= AppCookie.extractUser(flowSetId) & FLOWSET_MASK; + } + + // TODO: Mask in any other required fields here if (user_fields == 0) { return DEFAULT_FORWARDING_COOKIE; @@ -181,7 +309,7 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF * @return A collection of masked cookies suitable for flow-mod operations */ protected Collection<Masked<U64>> convertRoutingDecisionDescriptors(Iterable<Masked<U64>> maskedDescriptors) { - if(maskedDescriptors == null) { + if (maskedDescriptors == null) { return null; } @@ -194,7 +322,7 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF resultBuilder.add( Masked.of( - AppCookie.makeCookie(FORWARDING_APP_ID, (int)user_value), + AppCookie.makeCookie(FORWARDING_APP_ID, user_value), AppCookie.getAppFieldMask().or(U64.of(user_mask)) ) ); @@ -247,9 +375,16 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF protected void doDropFlow(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) { OFPort inPort = OFMessageUtils.getInPort(pi); 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 + OFFlowMod.Builder fmb = sw.getOFFactory().buildFlowAdd(); List<OFAction> actions = new ArrayList<OFAction>(); // set no action to drop - U64 cookie = makeForwardingCookie(decision); + U64 flowSetId = flowSetIdRegistry.generateFlowSetId(); + U64 cookie = makeForwardingCookie(decision, flowSetId); + + /* If link goes down, we'll remember to remove this flow */ + if (! m.isFullyWildcarded(MatchField.IN_PORT)) { + flowSetIdRegistry.registerFlowSetId(new NodePortTuple(sw.getId(), m.get(MatchField.IN_PORT)), flowSetId); + } + log.info("Dropping"); fmb.setCookie(cookie) .setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT) @@ -284,7 +419,7 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF doFlood(sw, pi, decision, cntx); return; } - + if (srcDevice == null) { log.error("No device entry found for source device. Is the device manager running? If so, report bug."); return; @@ -298,7 +433,7 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF doFlood(sw, pi, decision, cntx); return; } - + /* This packet-in is from a switch in the path before its flow was installed along the path */ if (!topologyService.isEdge(srcSw, srcPort)) { log.debug("Packet destination is known, but packet was not received on an edge port (rx on {}/{}). Flooding packet", srcSw, srcPort); @@ -343,7 +478,8 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF return; } - U64 cookie = makeForwardingCookie(decision); + U64 flowSetId = flowSetIdRegistry.generateFlowSetId(); + U64 cookie = makeForwardingCookie(decision, flowSetId); Path path = routingEngineService.getPath(srcSw, srcPort, dstAp.getNodeId(), @@ -367,18 +503,26 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF } else { /* Route traverses no links --> src/dst devices on same switch */ log.debug("Could not compute route. Devices should be on same switch src={} and dst={}", srcDevice, dstDevice); - Path p = new Path(srcDevice.getAttachmentPoints()[0].getNodeId(), dstDevice.getAttachmentPoints()[0].getNodeId()); + path = new Path(srcDevice.getAttachmentPoints()[0].getNodeId(), dstDevice.getAttachmentPoints()[0].getNodeId()); List<NodePortTuple> npts = new ArrayList<NodePortTuple>(2); npts.add(new NodePortTuple(srcDevice.getAttachmentPoints()[0].getNodeId(), srcDevice.getAttachmentPoints()[0].getPortId())); npts.add(new NodePortTuple(dstDevice.getAttachmentPoints()[0].getNodeId(), dstDevice.getAttachmentPoints()[0].getPortId())); - p.setPath(npts); - pushRoute(p, m, pi, sw.getId(), cookie, + path.setPath(npts); + pushRoute(path, m, pi, sw.getId(), cookie, cntx, requestFlowRemovedNotifn, OFFlowModCommand.ADD); } + /* + * Register this flowset with ingress and egress ports for link down + * flow removal. This is done after we push the path as it is blocking. + */ + for (NodePortTuple npt : path.getPath()) { + flowSetIdRegistry.registerFlowSetId(npt, flowSetId); + } + } /** @@ -593,6 +737,8 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF this.switchService = context.getServiceImpl(IOFSwitchService.class); this.linkService = context.getServiceImpl(ILinkDiscoveryService.class); + flowSetIdRegistry = FlowSetIdRegistry.getInstance(); + Map<String, String> configParameters = context.getConfigParams(this); String tmp = configParameters.get("hard-timeout"); if (tmp != null) { @@ -777,23 +923,66 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF IOFSwitch srcSw = switchService.getSwitch(u.getSrc()); /* src side of link */ if (srcSw != null) { - /* flows matching on src port */ - msgs.add(srcSw.getOFFactory().buildFlowDelete() - .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(DEFAULT_FORWARDING_COOKIE) - .setCookieMask(AppCookie.getAppFieldMask()) - .setOutPort(u.getSrcPort()) - .build()); - messageDamper.write(srcSw, msgs); - log.warn("{}. Removing flows to/from DPID={}, port={}", new Object[] { u.getType(), u.getSrc(), u.getSrcPort() }); + Set<U64> ids = flowSetIdRegistry.getFlowSetIds( + new NodePortTuple(u.getSrc(), u.getSrcPort())); + if (ids != null) { + Iterator<U64> i = ids.iterator(); + while (i.hasNext()) { + U64 id = i.next(); + U64 cookie = id.or(DEFAULT_FORWARDING_COOKIE); + U64 cookieMask = U64.of(FLOWSET_MASK).or(AppCookie.getAppFieldMask()); + /* flows matching on src port */ + msgs.add(srcSw.getOFFactory().buildFlowDelete() + .setCookie(cookie) + .setCookieMask(cookieMask) + .setMatch(srcSw.getOFFactory().buildMatch() + .setExact(MatchField.IN_PORT, u.getSrcPort()) + .build()) + .build()); + /* flows outputting to src port */ + msgs.add(srcSw.getOFFactory().buildFlowDelete() + .setCookie(cookie) + .setCookieMask(cookieMask) + .setOutPort(u.getSrcPort()) + .build()); + messageDamper.write(srcSw, msgs); + log.debug("src: Removing flows to/from DPID={}, port={}", u.getSrc(), u.getSrcPort()); + log.debug("src: Cookie/mask {}/{}", cookie, cookieMask); + + /* + * Now, for each ID on this particular failed link, remove + * all other flows in the network using this ID. + */ + Set<NodePortTuple> npts = flowSetIdRegistry.getNodePortTuples(id); + if (npts != null) { + for (NodePortTuple npt : npts) { + msgs.clear(); + IOFSwitch sw = switchService.getSwitch(npt.getNodeId()); + if (sw != null) { + msgs.add(sw.getOFFactory().buildFlowDelete() + .setCookie(cookie) + .setCookieMask(cookieMask) + .setMatch(sw.getOFFactory().buildMatch() + .setExact(MatchField.IN_PORT, npt.getPortId()) + .build()) + .build()); + /* flows outputting to port */ + msgs.add(sw.getOFFactory().buildFlowDelete() + .setCookie(cookie) + .setCookieMask(cookieMask) + .setOutPort(npt.getPortId()) + .build()); + messageDamper.write(sw, msgs); + log.debug("src: Removing same-cookie flows to/from DPID={}, port={}", npt.getNodeId(), npt.getPortId()); + log.debug("src: Cookie/mask {}/{}", cookie, cookieMask); + } + } + } + flowSetIdRegistry.removeExpiredFlowSetId(id, new NodePortTuple(u.getSrc(), u.getSrcPort()), i); + } + } } + flowSetIdRegistry.removeNodePortTuple(new NodePortTuple(u.getSrc(), u.getSrcPort())); } /* must be a link, not just a port down, if we have a dst switch */ @@ -801,24 +990,67 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule, IOF /* dst side of link */ IOFSwitch dstSw = switchService.getSwitch(u.getDst()); if (dstSw != null) { - /* flows matching on dst port */ - msgs.clear(); - msgs.add(dstSw.getOFFactory().buildFlowDelete() - .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(DEFAULT_FORWARDING_COOKIE) - .setCookieMask(AppCookie.getAppFieldMask()) - .setOutPort(u.getDstPort()) - .build()); - messageDamper.write(dstSw, msgs); - log.warn("{}. Removing flows to/from DPID={}, port={}", new Object[] { u.getType(), u.getDst(), u.getDstPort() }); + Set<U64> ids = flowSetIdRegistry.getFlowSetIds( + new NodePortTuple(u.getDst(), u.getDstPort())); + if (ids != null) { + Iterator<U64> i = ids.iterator(); + while (i.hasNext()) { + U64 id = i.next(); + U64 cookie = id.or(DEFAULT_FORWARDING_COOKIE); + U64 cookieMask = U64.of(FLOWSET_MASK).or(AppCookie.getAppFieldMask()); + /* flows matching on dst port */ + msgs.clear(); + msgs.add(dstSw.getOFFactory().buildFlowDelete() + .setCookie(cookie) + .setCookieMask(cookieMask) + .setMatch(dstSw.getOFFactory().buildMatch() + .setExact(MatchField.IN_PORT, u.getDstPort()) + .build()) + .build()); + /* flows outputting to dst port */ + msgs.add(dstSw.getOFFactory().buildFlowDelete() + .setCookie(cookie) + .setCookieMask(cookieMask) + .setOutPort(u.getDstPort()) + .build()); + messageDamper.write(dstSw, msgs); + log.debug("dst: Removing flows to/from DPID={}, port={}", u.getDst(), u.getDstPort()); + log.debug("dst: Cookie/mask {}/{}", cookie, cookieMask); + + /* + * Now, for each ID on this particular failed link, remove + * all other flows in the network using this ID. + */ + Set<NodePortTuple> npts = flowSetIdRegistry.getNodePortTuples(id); + if (npts != null) { + for (NodePortTuple npt : npts) { + msgs.clear(); + IOFSwitch sw = switchService.getSwitch(npt.getNodeId()); + if (sw != null) { + msgs.add(sw.getOFFactory().buildFlowDelete() + .setCookie(cookie) + .setCookieMask(cookieMask) + .setMatch(sw.getOFFactory().buildMatch() + .setExact(MatchField.IN_PORT, npt.getPortId()) + .build()) + .build()); + /* flows outputting to port */ + msgs.add(sw.getOFFactory().buildFlowDelete() + .setCookie(cookie) + .setCookieMask(cookieMask) + .setOutPort(npt.getPortId()) + .build()); + messageDamper.write(sw, msgs); + log.debug("dst: Removing same-cookie flows to/from DPID={}, port={}", npt.getNodeId(), npt.getPortId()); + log.debug("dst: Cookie/mask {}/{}", cookie, cookieMask); + } + } + } + flowSetIdRegistry.removeExpiredFlowSetId(id, new NodePortTuple(u.getDst(), u.getDstPort()), i); + } + } } + flowSetIdRegistry.removeNodePortTuple(new NodePortTuple(u.getDst(), u.getDstPort())); } } } diff --git a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java index 9b8299466a966b556ea17676e14025b93b7e0aa1..cfb7f8eddf46bf5bac79f39df444666c28d3a728 100644 --- a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java +++ b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java @@ -19,7 +19,6 @@ package net.floodlightcontroller.routing; import java.util.EnumSet; import java.util.ArrayList; -import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -45,7 +44,6 @@ import net.floodlightcontroller.util.FlowModUtils; import net.floodlightcontroller.util.MatchUtils; import net.floodlightcontroller.util.OFDPAUtils; import net.floodlightcontroller.util.OFMessageDamper; -import net.floodlightcontroller.util.TimedCache; import org.projectfloodlight.openflow.protocol.OFFlowMod; import org.projectfloodlight.openflow.protocol.match.Match; @@ -76,14 +74,11 @@ import org.slf4j.LoggerFactory; public abstract class ForwardingBase implements IOFMessageListener { protected static Logger log = LoggerFactory.getLogger(ForwardingBase.class); - protected static int OFMESSAGE_DAMPER_CAPACITY = 10000; // TODO: find sweet spot - protected static int OFMESSAGE_DAMPER_TIMEOUT = 250; // ms - public static int FLOWMOD_DEFAULT_IDLE_TIMEOUT = 5; // in seconds public static int FLOWMOD_DEFAULT_HARD_TIMEOUT = 0; // infinite public static int FLOWMOD_DEFAULT_PRIORITY = 1; // 0 is the default table-miss flow in OF1.3+, so we need to use 1 - public static TableId FLOWMOD_DEFAULT_TABLE_ID = TableId.ZERO; + protected static TableId FLOWMOD_DEFAULT_TABLE_ID = TableId.ZERO; protected static boolean FLOWMOD_DEFAULT_SET_SEND_FLOW_REM_FLAG = false; @@ -100,9 +95,6 @@ public abstract class ForwardingBase implements IOFMessageListener { protected static boolean FLOWMOD_DEFAULT_MATCH_TRANSPORT_SRC = true; protected static boolean FLOWMOD_DEFAULT_MATCH_TRANSPORT_DST = true; - protected static final short FLOWMOD_DEFAULT_IDLE_TIMEOUT_CONSTANT = 5; - protected static final short FLOWMOD_DEFAULT_HARD_TIMEOUT_CONSTANT = 0; - protected static boolean FLOOD_ALL_ARP_PACKETS = false; protected static boolean REMOVE_FLOWS_ON_LINK_OR_PORT_DOWN = true; @@ -115,33 +107,17 @@ public abstract class ForwardingBase implements IOFMessageListener { protected IDebugCounterService debugCounterService; protected ILinkDiscoveryService linkService; - protected OFMessageDamper messageDamper; - - // for broadcast loop suppression - protected boolean broadcastCacheFeature = true; - public final int prime1 = 2633; // for hash calculation - public final static int prime2 = 4357; // for hash calculation - public TimedCache<Long> broadcastCache = new TimedCache<Long>(100, 5*1000); // 5 seconds interval; - // flow-mod - for use in the cookie - public static final int FORWARDING_APP_ID = 2; // TODO: This must be managed - // by a global APP_ID class + public static final int FORWARDING_APP_ID = 2; static { - AppCookie.registerApp(FORWARDING_APP_ID, "Forwarding"); + AppCookie.registerApp(FORWARDING_APP_ID, "forwarding"); } - public static final U64 appCookie = AppCookie.makeCookie(FORWARDING_APP_ID, 0); - - // Comparator for sorting by SwitchCluster - public Comparator<SwitchPort> clusterIdComparator = - new Comparator<SwitchPort>() { - @Override - public int compare(SwitchPort d1, SwitchPort d2) { - DatapathId d1ClusterId = topologyService.getClusterId(d1.getNodeId()); - DatapathId d2ClusterId = topologyService.getClusterId(d2.getNodeId()); - return d1ClusterId.compareTo(d2ClusterId); - } - }; + protected static final U64 DEFAULT_FORWARDING_COOKIE = AppCookie.makeCookie(FORWARDING_APP_ID, 0); + protected OFMessageDamper messageDamper; + private static int OFMESSAGE_DAMPER_CAPACITY = 10000; + private static int OFMESSAGE_DAMPER_TIMEOUT = 250; // ms + protected void init() { messageDamper = new OFMessageDamper(OFMESSAGE_DAMPER_CAPACITY, EnumSet.of(OFType.FLOW_MOD), @@ -277,7 +253,7 @@ public abstract class ForwardingBase implements IOFMessageListener { if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) != 0) { fmb.setTableId(FLOWMOD_DEFAULT_TABLE_ID); } - + if (log.isTraceEnabled()) { log.trace("Pushing Route flowmod routeIndx={} " + "sw={} inPort={} outPort={}", diff --git a/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java b/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java index 5d0e22b6447ebcd88ce8c937d4ea430f1e053ee8..2b16e8bf6c2ef7a3e40e0a796194a6d412e1a602 100644 --- a/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java +++ b/src/test/java/net/floodlightcontroller/core/util/AppCookieTest.java @@ -41,10 +41,10 @@ public class AppCookieTest { @Test public void testAppCookie(){ - int user = 0xF123F123; // MSB set - int user2 = 0x42; // MSB cleared - U64 expectedCookie11 = U64.of(0xF4200000F123F123L); // app1, user1 - U64 expectedCookie21 = U64.of(0x74300000F123F123L); // app2, user1 + long user = 0xF123F123F1234L; // MSB set + long user2 = 0x42L; // MSB cleared + U64 expectedCookie11 = U64.of(0xF42F123F123F1234L); // app1, user1 + U64 expectedCookie21 = U64.of(0x743F123F123F1234L); // app2, user1 U64 expectedCookie12 = U64.of(0xF420000000000042L); // app1, user2 U64 expectedCookie22 = U64.of(0x7430000000000042L); // app2, user2 String name = "FooBar"; diff --git a/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java b/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java index 027d688b32d8415de450c80c2b619cbe3a4afe50..024f4c6644a3de36c5ecc291aa7c2597955b4d5e 100644 --- a/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java +++ b/src/test/java/net/floodlightcontroller/firewall/FirewallTest.java @@ -98,9 +98,9 @@ public class FirewallTest extends FloodlightTestCase { 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); + private static final U64 DENY_BCAST_COOKIE = AppCookie.makeCookie(APP_ID, 0xaaaaaaL); + private static final U64 ALLOW_BCAST_COOKIE = AppCookie.makeCookie(APP_ID, 0x555555L); + private static final U64 RULE_MISS_COOKIE = AppCookie.makeCookie(APP_ID, 0xffffffL); @Override @@ -684,10 +684,11 @@ public class FirewallTest extends FloodlightTestCase { @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("DENY_BCAST_COOKIE user_id is not correct", 0xaaaaaaL, 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("ALLOW_BCAST_COOKIE user_id is not correct", 0x555555L, 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)); + assertEquals("RULE_MISS_COOKIE user_id is not correct", 0xffffffL, 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 d8dbedcd8b22eee6993509ee154772b021db89e9..b4f022e7ce6c3f3978320a65c1af10812ff95931 100644 --- a/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java +++ b/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java @@ -173,6 +173,7 @@ public class ForwardingTest extends FloodlightTestCase { linkService.startUp(fmc); deviceManager.startUp(fmc); forwarding.startUp(fmc); + Forwarding.flowSetIdRegistry.seedFlowSetIdForUnitTest(3); entityClassifier.startUp(fmc); verify(topology); @@ -498,7 +499,7 @@ public class ForwardingTest extends FloodlightTestCase { .setActions(actions) .setOutPort(action.getPort()) .setBufferId(OFBufferId.NO_BUFFER) - .setCookie(U64.of(2L << 52)) + .setCookie(U64.of(2L << 52).or(U64.of(4 << Forwarding.FLOWSET_SHIFT))) .setPriority(1) .build(); OFFlowMod fm2 = fm1.createBuilder().build(); @@ -569,7 +570,7 @@ public class ForwardingTest extends FloodlightTestCase { .setActions(actions) .setOutPort(action.getPort()) .setBufferId(OFBufferId.NO_BUFFER) - .setCookie(U64.of(2L << 52)) + .setCookie(U64.of(2L << 52).or(U64.of(4 << Forwarding.FLOWSET_SHIFT))) .setPriority(1) .build(); OFFlowMod fm2 = fm1.createBuilder().build(); @@ -637,7 +638,7 @@ public class ForwardingTest extends FloodlightTestCase { .setActions(actions) .setOutPort(OFPort.of(3)) .setBufferId(OFBufferId.NO_BUFFER) - .setCookie(U64.of(2L<< 52)) + .setCookie(U64.of(2L << 52).or(U64.of(4 << Forwarding.FLOWSET_SHIFT))) .setPriority(1) .build(); @@ -694,7 +695,7 @@ public class ForwardingTest extends FloodlightTestCase { .setActions(actions) .setOutPort(OFPort.of(3)) .setBufferId(OFBufferId.NO_BUFFER) - .setCookie(U64.of(2L<< 52)) + .setCookie(U64.of(2L << 52).or(U64.of(4 << Forwarding.FLOWSET_SHIFT))) .setPriority(1) .build(); @@ -753,21 +754,20 @@ public class ForwardingTest extends FloodlightTestCase { .setActions(actions) .setOutPort(OFPort.of(3)) .setBufferId(OFBufferId.NO_BUFFER) - .setCookie(U64.of(2L << 52)) + .setCookie(U64.of(2L << 52).or(U64.of(6 << Forwarding.FLOWSET_SHIFT))) .setXid(anyLong()) .build(); // 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); - expectLastCall().times(1); + expect(sw1.write(fm1)).andReturn(true).once(); // Update new expected XID - sw1.write(packetOut.createBuilder().setXid(anyLong()).build()); - expectLastCall().times(3); + expect(sw1.write(packetOut.createBuilder().setXid(anyLong()).build())).andReturn(true).times(3); reset(topology); - expect(topology.isBroadcastAllowed(DatapathId.of(anyLong()), OFPort.of(anyShort()))).andReturn(true).anyTimes(); + expect(topology.isBroadcastAllowed(DatapathId.of(anyLong()), OFPort.of(anyInt()))).andReturn(true).anyTimes(); + expect(topology.isEdge(DatapathId.of(anyLong()), OFPort.of(anyInt()))).andReturn(true).anyTimes(); expect(topology.getClusterId(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(); @@ -881,7 +881,7 @@ public class ForwardingTest extends FloodlightTestCase { .setActions(actions) .setOutPort(OFPort.of(3)) .setBufferId(OFBufferId.NO_BUFFER) - .setCookie(U64.of(2L<< 52)) + .setCookie(U64.of(2L << 52).or(U64.of(4 << Forwarding.FLOWSET_SHIFT))) .setPriority(1) .build(); @@ -940,7 +940,7 @@ public class ForwardingTest extends FloodlightTestCase { .setActions(actions) .setOutPort(OFPort.of(3)) .setBufferId(OFBufferId.NO_BUFFER) - .setCookie(U64.of(2L<< 52 | 0xFFffFFffL)) + .setCookie(U64.of(2L << 52).or(U64.of(4 << Forwarding.FLOWSET_SHIFT).or(U64.of(0xFFffFFL)))) .setPriority(1) .build(); @@ -995,7 +995,7 @@ public class ForwardingTest extends FloodlightTestCase { Masked<U64> masked_cookie = Masked.of( AppCookie.makeCookie(Forwarding.FORWARDING_APP_ID, (int)4294967295L), - AppCookie.getAppFieldMask().or(U64.of(0xffffffffL))); + AppCookie.getAppFieldMask().or(U64.of(0xffffffL))); List<OFMessage> msgs_test = new ArrayList<>(); msgs_test.add( factory.buildFlowDelete() .setCookie(masked_cookie.getValue()) @@ -1015,10 +1015,10 @@ public class ForwardingTest extends FloodlightTestCase { 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 + U64.of(0x0000000000ffFFffL), + U64.of(0x0020000000ffFFffL))); // User mask = 0xffFFffFFL which is forwarding.DECISION_MASK/AppCookie.USER_MASK descriptors.add(Masked.of( - U64.of(0x00000000FFffFFffL), + U64.of(0x0000000000ffFFffL), U64.of(0x0020000000000000L))); expect(sw1.getStatus()).andReturn(IOFSwitch.SwitchStatus.MASTER).anyTimes(); @@ -1036,7 +1036,7 @@ public class ForwardingTest extends FloodlightTestCase { // Cookies Masked<U64> masked_cookie = Masked.of( AppCookie.makeCookie(Forwarding.FORWARDING_APP_ID, (int)4294967295L), - AppCookie.getAppFieldMask().or(U64.of(0xffffffffL))); + AppCookie.getAppFieldMask().or(U64.of(0xffffffL))); 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