/**
* Copyright 2011, Big Switch Networks, Inc.
* Originally created by David Erickson, Stanford University
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
**/
package net.floodlightcontroller.forwarding;
import 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;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.IOFSwitchListener;
import net.floodlightcontroller.core.PortChangeType;
import net.floodlightcontroller.core.internal.IOFSwitchService;
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.types.NodePortTuple;
import net.floodlightcontroller.core.util.AppCookie;
import net.floodlightcontroller.debugcounter.IDebugCounterService;
import net.floodlightcontroller.devicemanager.IDevice;
import net.floodlightcontroller.devicemanager.IDeviceService;
import net.floodlightcontroller.devicemanager.SwitchPort;
import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryListener;
import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
import net.floodlightcontroller.packet.Ethernet;
import net.floodlightcontroller.packet.IPv4;
import net.floodlightcontroller.packet.IPv6;
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.Path;
import net.floodlightcontroller.topology.ITopologyService;
import net.floodlightcontroller.util.FlowModUtils;
import net.floodlightcontroller.util.OFDPAUtils;
import net.floodlightcontroller.util.OFMessageUtils;
import net.floodlightcontroller.util.OFPortMode;
import net.floodlightcontroller.util.OFPortModeTuple;
import net.floodlightcontroller.util.ParseUtils;
import org.projectfloodlight.openflow.protocol.OFFlowMod;
import org.projectfloodlight.openflow.protocol.OFFlowModCommand;
import org.projectfloodlight.openflow.protocol.OFGroupType;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFPacketIn;
import org.projectfloodlight.openflow.protocol.OFPacketOut;
import org.projectfloodlight.openflow.protocol.OFPortDesc;
import org.projectfloodlight.openflow.protocol.OFVersion;
import org.projectfloodlight.openflow.protocol.action.OFAction;
import org.projectfloodlight.openflow.protocol.match.Match;
import org.projectfloodlight.openflow.protocol.match.MatchField;
import org.projectfloodlight.openflow.types.DatapathId;
import org.projectfloodlight.openflow.types.EthType;
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;
import org.projectfloodlight.openflow.types.OFVlanVidMatch;
import org.projectfloodlight.openflow.types.TableId;
import org.projectfloodlight.openflow.types.U16;
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, IRoutingDecisionChangedListener {
protected static final Logger log = LoggerFactory.getLogger(Forwarding.class);
/*
* 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();
boolean removed = false;
while (itr.hasNext()) {
Entry<NodePortTuple, Set<U64>> e = itr.next();
if (e.getKey().equals(avoid) && ! removed) {
avoidItr.remove();
removed = true;
} else {
Set<U64> ids = e.getValue();
ids.remove(flowSetId);
}
}
}
}
@Override
public Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx) {
Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
// We found a routing decision (i.e. Firewall is enabled... it's the only thing that makes RoutingDecisions)
if (decision != null) {
if (log.isTraceEnabled()) {
log.trace("Forwarding decision={} was made for PacketIn={}", decision.getRoutingAction().toString(), pi);
}
switch(decision.getRoutingAction()) {
case NONE:
// don't do anything
return Command.CONTINUE;
case FORWARD_OR_FLOOD:
case FORWARD:
doForwardFlow(sw, pi, decision, cntx, false);
return Command.CONTINUE;
case MULTICAST:
// treat as broadcast
doFlood(sw, pi, decision, cntx);
return Command.CONTINUE;
case DROP:
doDropFlow(sw, pi, decision, cntx);
return Command.CONTINUE;
default:
log.error("Unexpected decision made for this packet-in={}", pi, decision.getRoutingAction());
return Command.CONTINUE;
}
} else { // No routing decision was found. Forward to destination or flood if bcast or mcast.
if (log.isTraceEnabled()) {
log.trace("No decision was made for PacketIn={}, forwarding", pi);
}
if (eth.isBroadcast() || eth.isMulticast()) {
doFlood(sw, pi, decision, cntx);
} else {
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 fields masked-in
*/
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;
}
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;
}
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, 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 = OFMessageUtils.getInPort(pi);
Match m = createMatchFromPacket(sw, inPort, pi, cntx);
OFFlowMod.Builder fmb = sw.getOFFactory().buildFlowAdd();
List<OFAction> actions = new ArrayList<OFAction>(); // set no action to drop
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)
.setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT)
.setBufferId(OFBufferId.NO_BUFFER)
.setMatch(m)
.setPriority(FLOWMOD_DEFAULT_PRIORITY);
FlowModUtils.setActions(fmb, actions, sw);
/* Configure for particular switch pipeline */
if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) != 0) {
fmb.setTableId(FLOWMOD_DEFAULT_TABLE_ID);
}
if (log.isDebugEnabled()) {
log.debug("write drop flow-mod sw={} match={} flow-mod={}",
new Object[] { sw, m, fmb.build() });
}
boolean dampened = messageDamper.write(sw, fmb.build());
log.debug("OFMessage dampened: {}", dampened);
}
protected void doForwardFlow(IOFSwitch sw, OFPacketIn pi, IRoutingDecision decision, FloodlightContext cntx, boolean requestFlowRemovedNotifn) {
OFPort srcPort = OFMessageUtils.getInPort(pi);
DatapathId srcSw = sw.getId();
IDevice dstDevice = IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_DST_DEVICE);
IDevice srcDevice = IDeviceService.fcStore.get(cntx, IDeviceService.CONTEXT_SRC_DEVICE);
if (dstDevice == null) {
log.debug("Destination device unknown. Flooding packet");
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;
}
/* Some physical switches partially support or do not support ARP flows */
if (FLOOD_ALL_ARP_PACKETS &&
IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD).getEtherType()
== EthType.ARP) {
log.debug("ARP flows disabled in Forwarding. Flooding ARP packet");
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);
doFlood(sw, pi, decision, cntx);
return;
}
/*
* Search for the true attachment point. The true AP is
* not an endpoint of a link. It is a switch port w/o an
* associated link. Note this does not necessarily hold
* true for devices that 'live' between OpenFlow islands.
*
* TODO Account for the case where a device is actually
* attached between islands (possibly on a non-OF switch
* in between two OpenFlow switches).
*/
SwitchPort dstAp = null;
for (SwitchPort ap : dstDevice.getAttachmentPoints()) {
if (topologyService.isEdge(ap.getNodeId(), ap.getPortId())) {
dstAp = ap;
break;
}
}
/*
* This should only happen (perhaps) when the controller is
* actively learning a new topology and hasn't discovered
* all links yet, or a switch was in standalone mode and the
* packet in question was captured in flight on the dst point
* of a link.
*/
if (dstAp == null) {
log.debug("Could not locate edge attachment point for destination device {}. Flooding packet");
doFlood(sw, pi, decision, cntx);
return;
}
/* Validate that the source and destination are not on the same switch port */
if (sw.getId().equals(dstAp.getNodeId()) && srcPort.equals(dstAp.getPortId())) {
log.info("Both source and destination are on the same switch/port {}/{}. Dropping packet", sw.toString(), srcPort);
return;
}
U64 flowSetId = flowSetIdRegistry.generateFlowSetId();
U64 cookie = makeForwardingCookie(decision, flowSetId);
Path path = routingEngineService.getPath(srcSw,
srcPort,
dstAp.getNodeId(),
dstAp.getPortId());
Match m = createMatchFromPacket(sw, srcPort, pi, cntx);
if (! path.getPath().isEmpty()) {
if (log.isDebugEnabled()) {
log.debug("pushRoute inPort={} route={} " +
"destination={}:{}",
new Object[] { srcPort, path,
dstAp.getNodeId(),
dstAp.getPortId()});
log.debug("Creating flow rules on the route, match rule: {}", m);
}
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);
}
} /* else no path was found */
}
/**
* Instead of using the Firewall's routing decision Match, which might be as general
* as "in_port" and inadvertently Match packets erroneously, construct a more
* specific Match based on the deserialized OFPacketIn's payload, which has been
* placed in the FloodlightContext already by the Controller.
*
* @param sw, the switch on which the packet was received
* @param inPort, the ingress switch port on which the packet was received
* @param cntx, the current context which contains the deserialized packet
* @return a composed Match object based on the provided information
*/
protected Match createMatchFromPacket(IOFSwitch sw, OFPort inPort, OFPacketIn pi, FloodlightContext cntx) {
// The packet in match will only contain the port number.
// We need to add in specifics for the hosts we're routing between.
Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
VlanVid vlan = null;
if (pi.getVersion().compareTo(OFVersion.OF_11) > 0 && /* 1.0 and 1.1 do not have a match */
pi.getMatch().get(MatchField.VLAN_VID) != null) {
vlan = pi.getMatch().get(MatchField.VLAN_VID).getVlanVid(); /* VLAN may have been popped by switch */
}
if (vlan == null) {
vlan = VlanVid.ofVlan(eth.getVlanID()); /* VLAN might still be in packet */
}
MacAddress srcMac = eth.getSourceMACAddress();
MacAddress dstMac = eth.getDestinationMACAddress();
Match.Builder mb = sw.getOFFactory().buildMatch();
if (FLOWMOD_DEFAULT_MATCH_IN_PORT) {
mb.setExact(MatchField.IN_PORT, inPort);
}
if (FLOWMOD_DEFAULT_MATCH_MAC) {
if (FLOWMOD_DEFAULT_MATCH_MAC_SRC) {
mb.setExact(MatchField.ETH_SRC, srcMac);
}
if (FLOWMOD_DEFAULT_MATCH_MAC_DST) {
mb.setExact(MatchField.ETH_DST, dstMac);
}
}
if (FLOWMOD_DEFAULT_MATCH_VLAN) {
if (!vlan.equals(VlanVid.ZERO)) {
mb.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlanVid(vlan));
}
}
// TODO Detect switch type and match to create hardware-implemented flow
if (eth.getEtherType() == EthType.IPv4) { /* shallow check for equality is okay for EthType */
IPv4 ip = (IPv4) eth.getPayload();
IPv4Address srcIp = ip.getSourceAddress();
IPv4Address dstIp = ip.getDestinationAddress();
if (FLOWMOD_DEFAULT_MATCH_IP) {
mb.setExact(MatchField.ETH_TYPE, EthType.IPv4);
if (FLOWMOD_DEFAULT_MATCH_IP_SRC) {
mb.setExact(MatchField.IPV4_SRC, srcIp);
}
if (FLOWMOD_DEFAULT_MATCH_IP_DST) {
mb.setExact(MatchField.IPV4_DST, dstIp);
}
}
if (FLOWMOD_DEFAULT_MATCH_TRANSPORT) {
/*
* Take care of the ethertype if not included earlier,
* since it's a prerequisite for transport ports.
*/
if (!FLOWMOD_DEFAULT_MATCH_IP) {
mb.setExact(MatchField.ETH_TYPE, EthType.IPv4);
}
if (ip.getProtocol().equals(IpProtocol.TCP)) {
TCP tcp = (TCP) ip.getPayload();
mb.setExact(MatchField.IP_PROTO, IpProtocol.TCP);
if (FLOWMOD_DEFAULT_MATCH_TRANSPORT_SRC) {
mb.setExact(MatchField.TCP_SRC, tcp.getSourcePort());
}
if (FLOWMOD_DEFAULT_MATCH_TRANSPORT_DST) {
mb.setExact(MatchField.TCP_DST, tcp.getDestinationPort());
}
if(
sw.getSwitchDescription().getHardwareDescription().toLowerCase().contains("open vswitch") && (
Integer.parseInt(sw.getSwitchDescription().getSoftwareDescription().toLowerCase().split("\\.")[0]) > 2 || (
Integer.parseInt(sw.getSwitchDescription().getSoftwareDescription().toLowerCase().split("\\.")[0]) == 2 &&
Integer.parseInt(sw.getSwitchDescription().getSoftwareDescription().toLowerCase().split("\\.")[1]) >= 1 ))
){
if(FLOWMOD_DEFAULT_MATCH_TCP_FLAG){
mb.setExact(MatchField.OVS_TCP_FLAGS, U16.of(tcp.getFlags()));
}
}
} else if (ip.getProtocol().equals(IpProtocol.UDP)) {
UDP udp = (UDP) ip.getPayload();
mb.setExact(MatchField.IP_PROTO, IpProtocol.UDP);
if (FLOWMOD_DEFAULT_MATCH_TRANSPORT_SRC) {
mb.setExact(MatchField.UDP_SRC, udp.getSourcePort());
}
if (FLOWMOD_DEFAULT_MATCH_TRANSPORT_DST) {
mb.setExact(MatchField.UDP_DST, udp.getDestinationPort());
}
}
}
} else if (eth.getEtherType() == EthType.ARP) { /* shallow check for equality is okay for EthType */
mb.setExact(MatchField.ETH_TYPE, EthType.ARP);
} else if (eth.getEtherType() == EthType.IPv6) {
IPv6 ip = (IPv6) eth.getPayload();
IPv6Address srcIp = ip.getSourceAddress();
IPv6Address dstIp = ip.getDestinationAddress();
if (FLOWMOD_DEFAULT_MATCH_IP) {
mb.setExact(MatchField.ETH_TYPE, EthType.IPv6);
if (FLOWMOD_DEFAULT_MATCH_IP_SRC) {
mb.setExact(MatchField.IPV6_SRC, srcIp);
}
if (FLOWMOD_DEFAULT_MATCH_IP_DST) {
mb.setExact(MatchField.IPV6_DST, dstIp);
}
}
if (FLOWMOD_DEFAULT_MATCH_TRANSPORT) {
/*
* Take care of the ethertype if not included earlier,
* since it's a prerequisite for transport ports.
*/
if (!FLOWMOD_DEFAULT_MATCH_IP) {
mb.setExact(MatchField.ETH_TYPE, EthType.IPv6);
}
if (ip.getNextHeader().equals(IpProtocol.TCP)) {
TCP tcp = (TCP) ip.getPayload();
mb.setExact(MatchField.IP_PROTO, IpProtocol.TCP);
if (FLOWMOD_DEFAULT_MATCH_TRANSPORT_SRC) {
mb.setExact(MatchField.TCP_SRC, tcp.getSourcePort());
}
if (FLOWMOD_DEFAULT_MATCH_TRANSPORT_DST) {
mb.setExact(MatchField.TCP_DST, tcp.getDestinationPort());
}
if(
sw.getSwitchDescription().getHardwareDescription().toLowerCase().contains("open vswitch") && (
Integer.parseInt(sw.getSwitchDescription().getSoftwareDescription().toLowerCase().split("\\.")[0]) > 2 || (
Integer.parseInt(sw.getSwitchDescription().getSoftwareDescription().toLowerCase().split("\\.")[0]) == 2 &&
Integer.parseInt(sw.getSwitchDescription().getSoftwareDescription().toLowerCase().split("\\.")[1]) >= 1 ))
){
if(FLOWMOD_DEFAULT_MATCH_TCP_FLAG){
mb.setExact(MatchField.OVS_TCP_FLAGS, U16.of(tcp.getFlags()));
}
}
} else if (ip.getNextHeader().equals(IpProtocol.UDP)) {
UDP udp = (UDP) ip.getPayload();
mb.setExact(MatchField.IP_PROTO, IpProtocol.UDP);
if (FLOWMOD_DEFAULT_MATCH_TRANSPORT_SRC) {
mb.setExact(MatchField.UDP_SRC, udp.getSourcePort());
}
if (FLOWMOD_DEFAULT_MATCH_TRANSPORT_DST) {
mb.setExact(MatchField.UDP_DST, udp.getDestinationPort());
}
}
}
}
return mb.build();
}
/**
* Creates a OFPacketOut with the OFPacketIn data that is flooded on all ports unless
* 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, IRoutingDecision decision, FloodlightContext cntx) {
OFPort inPort = OFMessageUtils.getInPort(pi);
OFPacketOut.Builder pob = sw.getOFFactory().buildPacketOut();
List<OFAction> actions = new ArrayList<OFAction>();
Set<OFPort> broadcastPorts = this.topologyService.getSwitchBroadcastPorts(sw.getId());
if (broadcastPorts.isEmpty()) {
log.debug("No broadcast ports found. Using FLOOD output action");
broadcastPorts = Collections.singleton(OFPort.FLOOD);
}
for (OFPort p : broadcastPorts) {
if (p.equals(inPort)) continue;
actions.add(sw.getOFFactory().actions().output(p, Integer.MAX_VALUE));
}
pob.setActions(actions);
// log.info("actions {}",actions);
// set buffer-id, in-port and packet-data based on packet-in
pob.setBufferId(OFBufferId.NO_BUFFER);
OFMessageUtils.setInPort(pob, inPort);
pob.setData(pi.getData());
if (log.isTraceEnabled()) {
log.trace("Writing flood PacketOut switch={} packet-in={} packet-out={}",
new Object[] {sw, pi, pob.build()});
}
messageDamper.write(sw, pob.build());
return;
}
// IFloodlightModule methods
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
// We don't export any services
return null;
}
@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService>
getServiceImpls() {
// We don't have any services
return null;
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
Collection<Class<? extends IFloodlightService>> l =
new ArrayList<Class<? extends IFloodlightService>>();
l.add(IFloodlightProviderService.class);
l.add(IDeviceService.class);
l.add(IRoutingService.class);
l.add(ITopologyService.class);
l.add(IDebugCounterService.class);
l.add(ILinkDiscoveryService.class);
return l;
}
@Override
public void init(FloodlightModuleContext context) throws FloodlightModuleException {
super.init();
this.floodlightProviderService = context.getServiceImpl(IFloodlightProviderService.class);
this.deviceManagerService = context.getServiceImpl(IDeviceService.class);
this.routingEngineService = context.getServiceImpl(IRoutingService.class);
this.topologyService = context.getServiceImpl(ITopologyService.class);
this.debugCounterService = context.getServiceImpl(IDebugCounterService.class);
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) {
FLOWMOD_DEFAULT_HARD_TIMEOUT = ParseUtils.parseHexOrDecInt(tmp);
log.info("Default hard timeout set to {}.", FLOWMOD_DEFAULT_HARD_TIMEOUT);
} else {
log.info("Default hard timeout not configured. Using {}.", FLOWMOD_DEFAULT_HARD_TIMEOUT);
}
tmp = configParameters.get("idle-timeout");
if (tmp != null) {
FLOWMOD_DEFAULT_IDLE_TIMEOUT = ParseUtils.parseHexOrDecInt(tmp);
log.info("Default idle timeout set to {}.", FLOWMOD_DEFAULT_IDLE_TIMEOUT);
} else {
log.info("Default idle timeout not configured. Using {}.", FLOWMOD_DEFAULT_IDLE_TIMEOUT);
}
tmp = configParameters.get("table-id");
if (tmp != null) {
FLOWMOD_DEFAULT_TABLE_ID = TableId.of(ParseUtils.parseHexOrDecInt(tmp));
log.info("Default table ID set to {}.", FLOWMOD_DEFAULT_TABLE_ID);
} else {
log.info("Default table ID not configured. Using {}.", FLOWMOD_DEFAULT_TABLE_ID);
}
tmp = configParameters.get("priority");
if (tmp != null) {
FLOWMOD_DEFAULT_PRIORITY = ParseUtils.parseHexOrDecInt(tmp);
log.info("Default priority set to {}.", FLOWMOD_DEFAULT_PRIORITY);
} else {
log.info("Default priority not configured. Using {}.", FLOWMOD_DEFAULT_PRIORITY);
}
tmp = configParameters.get("set-send-flow-rem-flag");
if (tmp != null) {
FLOWMOD_DEFAULT_SET_SEND_FLOW_REM_FLAG = Boolean.parseBoolean(tmp);
log.info("Default flags will be set to SEND_FLOW_REM {}.", FLOWMOD_DEFAULT_SET_SEND_FLOW_REM_FLAG);
} else {
log.info("Default flags will be empty.");
}
tmp = configParameters.get("match");
if (tmp != null) {
tmp = tmp.toLowerCase();
if (!tmp.contains("in-port") && !tmp.contains("vlan")
&& !tmp.contains("mac") && !tmp.contains("ip")
&& !tmp.contains("transport") && !tmp.contains("flag")) {
/* leave the default configuration -- blank or invalid 'match' value */
} else {
FLOWMOD_DEFAULT_MATCH_IN_PORT = tmp.contains("in-port") ? true : false;
FLOWMOD_DEFAULT_MATCH_VLAN = tmp.contains("vlan") ? true : false;
FLOWMOD_DEFAULT_MATCH_MAC = tmp.contains("mac") ? true : false;
FLOWMOD_DEFAULT_MATCH_IP = tmp.contains("ip") ? true : false;
FLOWMOD_DEFAULT_MATCH_TRANSPORT = tmp.contains("transport") ? true : false;
FLOWMOD_DEFAULT_MATCH_TCP_FLAG = tmp.contains("flag") ? true : false;
}
}
log.info("Default flow matches set to: IN_PORT=" + FLOWMOD_DEFAULT_MATCH_IN_PORT
+ ", VLAN=" + FLOWMOD_DEFAULT_MATCH_VLAN
+ ", MAC=" + FLOWMOD_DEFAULT_MATCH_MAC
+ ", IP=" + FLOWMOD_DEFAULT_MATCH_IP
+ ", FLAG=" + FLOWMOD_DEFAULT_MATCH_TCP_FLAG
+ ", TPPT=" + FLOWMOD_DEFAULT_MATCH_TRANSPORT);
tmp = configParameters.get("detailed-match");
if (tmp != null) {
tmp = tmp.toLowerCase();
if (!tmp.contains("src-mac") && !tmp.contains("dst-mac")
&& !tmp.contains("src-ip") && !tmp.contains("dst-ip")
&& !tmp.contains("src-transport") && !tmp.contains("dst-transport")) {
/* leave the default configuration -- both src and dst for layers defined above */
} else {
FLOWMOD_DEFAULT_MATCH_MAC_SRC = tmp.contains("src-mac") ? true : false;
FLOWMOD_DEFAULT_MATCH_MAC_DST = tmp.contains("dst-mac") ? true : false;
FLOWMOD_DEFAULT_MATCH_IP_SRC = tmp.contains("src-ip") ? true : false;
FLOWMOD_DEFAULT_MATCH_IP_DST = tmp.contains("dst-ip") ? true : false;
FLOWMOD_DEFAULT_MATCH_TRANSPORT_SRC = tmp.contains("src-transport") ? true : false;
FLOWMOD_DEFAULT_MATCH_TRANSPORT_DST = tmp.contains("dst-transport") ? true : false;
}
}
log.info("Default detailed flow matches set to: SRC_MAC=" + FLOWMOD_DEFAULT_MATCH_MAC_SRC
+ ", DST_MAC=" + FLOWMOD_DEFAULT_MATCH_MAC_DST
+ ", SRC_IP=" + FLOWMOD_DEFAULT_MATCH_IP_SRC
+ ", DST_IP=" + FLOWMOD_DEFAULT_MATCH_IP_DST
+ ", SRC_TPPT=" + FLOWMOD_DEFAULT_MATCH_TRANSPORT_SRC
+ ", DST_TPPT=" + FLOWMOD_DEFAULT_MATCH_TRANSPORT_DST);
tmp = configParameters.get("flood-arp");
if (tmp != null) {
tmp = tmp.toLowerCase();
if (!tmp.contains("yes") && !tmp.contains("yep") && !tmp.contains("true") && !tmp.contains("ja") && !tmp.contains("stimmt")) {
FLOOD_ALL_ARP_PACKETS = false;
log.info("Not flooding ARP packets. ARP flows will be inserted for known destinations");
} else {
FLOOD_ALL_ARP_PACKETS = true;
log.info("Flooding all ARP packets. No ARP flows will be inserted");
}
}
tmp = configParameters.get("remove-flows-on-link-or-port-down");
if (tmp != null) {
REMOVE_FLOWS_ON_LINK_OR_PORT_DOWN = Boolean.parseBoolean(tmp);
}
if (REMOVE_FLOWS_ON_LINK_OR_PORT_DOWN) {
log.info("Flows will be removed on link/port down events");
} else {
log.info("Flows will not be removed on link/port down events");
}
}
@Override
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) {
linkService.addListener(this);
}
}
@Override
public void switchAdded(DatapathId switchId) {
}
@Override
public void switchRemoved(DatapathId switchId) {
}
@Override
public void switchActivated(DatapathId switchId) {
IOFSwitch sw = switchService.getSwitch(switchId);
if (sw == null) {
log.warn("Switch {} was activated but had no switch object in the switch service. Perhaps it quickly disconnected", switchId);
return;
}
if (OFDPAUtils.isOFDPASwitch(sw)) {
messageDamper.write(sw, sw.getOFFactory().buildFlowDelete()
.setTableId(TableId.ALL)
.build()
);
messageDamper.write(sw, sw.getOFFactory().buildGroupDelete()
.setGroup(OFGroup.ANY)
.setGroupType(OFGroupType.ALL)
.build()
);
messageDamper.write(sw, sw.getOFFactory().buildGroupDelete()
.setGroup(OFGroup.ANY)
.setGroupType(OFGroupType.INDIRECT)
.build()
);
messageDamper.write(sw, sw.getOFFactory().buildBarrierRequest().build());
List<OFPortModeTuple> portModes = new ArrayList<OFPortModeTuple>();
for (OFPortDesc p : sw.getPorts()) {
portModes.add(OFPortModeTuple.of(p.getPortNo(), OFPortMode.ACCESS));
}
if (log.isWarnEnabled()) {
log.warn("For OF-DPA switch {}, initializing VLAN {} on ports {}", new Object[] { switchId, VlanVid.ZERO, portModes});
}
OFDPAUtils.addLearningSwitchPrereqs(sw, VlanVid.ZERO, portModes);
}
}
@Override
public void switchPortChanged(DatapathId switchId, OFPortDesc port, PortChangeType type) {
/* Port down events handled via linkDiscoveryUpdate(), which passes thru all events */
}
@Override
public void switchChanged(DatapathId switchId) {
}
@Override
public void switchDeactivated(DatapathId switchId) {
}
@Override
public void linkDiscoveryUpdate(List<LDUpdate> updateList) {
for (LDUpdate u : updateList) {
/* Remove flows on either side if link/port went down */
if (u.getOperation() == UpdateOperation.LINK_REMOVED ||
u.getOperation() == UpdateOperation.PORT_DOWN ||
u.getOperation() == UpdateOperation.TUNNEL_PORT_REMOVED) {
Set<OFMessage> msgs = new HashSet<OFMessage>();
if (u.getSrc() != null && !u.getSrc().equals(DatapathId.NONE)) {
IOFSwitch srcSw = switchService.getSwitch(u.getSrc());
/* src side of link */
if (srcSw != null) {
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 */
if (u.getDst() != null && !u.getDst().equals(DatapathId.NONE)) {
/* dst side of link */
IOFSwitch dstSw = switchService.getSwitch(u.getDst());
if (dstSw != null) {
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()));
}
}
}
}
}