blob: 18f5d0a7ca38a135c5eba160424dd69734234a01 [file] [log] [blame]
/*
* Copyright 2014 Open Networking Laboratory
*
* 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 org.onlab.onos.fwd;
import static org.slf4j.LoggerFactory.getLogger;
import java.util.Dictionary;
import java.util.Set;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.onlab.onos.core.ApplicationId;
import org.onlab.onos.core.CoreService;
import org.onlab.onos.net.Host;
import org.onlab.onos.net.HostId;
import org.onlab.onos.net.Path;
import org.onlab.onos.net.PortNumber;
import org.onlab.onos.net.flow.DefaultFlowRule;
import org.onlab.onos.net.flow.DefaultTrafficSelector;
import org.onlab.onos.net.flow.DefaultTrafficTreatment;
import org.onlab.onos.net.flow.FlowRule;
import org.onlab.onos.net.flow.FlowRuleService;
import org.onlab.onos.net.flow.TrafficSelector;
import org.onlab.onos.net.flow.TrafficTreatment;
import org.onlab.onos.net.host.HostService;
import org.onlab.onos.net.packet.InboundPacket;
import org.onlab.onos.net.packet.PacketContext;
import org.onlab.onos.net.packet.PacketProcessor;
import org.onlab.onos.net.packet.PacketService;
import org.onlab.onos.net.topology.TopologyService;
import org.onlab.packet.Ethernet;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
/**
* Sample reactive forwarding application.
*/
@Component(immediate = true)
public class ReactiveForwarding {
private static final int TIMEOUT = 10;
private static final int PRIORITY = 10;
private final Logger log = getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected TopologyService topologyService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected HostService hostService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FlowRuleService flowRuleService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
private ReactivePacketProcessor processor = new ReactivePacketProcessor();
private ApplicationId appId;
@Property(name = "enabled", boolValue = true,
label = "Enable forwarding; default is true")
private boolean isEnabled = true;
@Activate
public void activate() {
appId = coreService.registerApplication("org.onlab.onos.fwd");
packetService.addProcessor(processor, PacketProcessor.ADVISOR_MAX + 2);
log.info("Started with Application ID {}", appId.id());
}
@Deactivate
public void deactivate() {
flowRuleService.removeFlowRulesById(appId);
packetService.removeProcessor(processor);
processor = null;
log.info("Stopped");
}
@Modified
public void modified(ComponentContext context) {
Dictionary properties = context.getProperties();
String flag = (String) properties.get("enabled");
if (flag != null) {
boolean enabled = flag.equals("true");
if (isEnabled != enabled) {
isEnabled = enabled;
if (!isEnabled) {
flowRuleService.removeFlowRulesById(appId);
}
log.info("Reconfigured. Forwarding is {}",
isEnabled ? "enabled" : "disabled");
}
}
}
/**
* Packet processor responsible for forwarding packets along their paths.
*/
private class ReactivePacketProcessor implements PacketProcessor {
@Override
public void process(PacketContext context) {
// Stop processing if the packet has been handled, since we
// can't do any more to it.
if (!isEnabled || context.isHandled()) {
return;
}
InboundPacket pkt = context.inPacket();
Ethernet ethPkt = pkt.parsed();
// Bail if this is deemed to be a control packet.
if (isControlPacket(ethPkt)) {
return;
}
HostId id = HostId.hostId(ethPkt.getDestinationMAC());
// Do not process link-local addresses in any way.
if (id.mac().isLinkLocal()) {
return;
}
// Do we know who this is for? If not, flood and bail.
Host dst = hostService.getHost(id);
if (dst == null) {
flood(context);
return;
}
// Are we on an edge switch that our destination is on? If so,
// simply forward out to the destination and bail.
if (pkt.receivedFrom().deviceId().equals(dst.location().deviceId())) {
if (!context.inPacket().receivedFrom().port().equals(dst.location().port())) {
installRule(context, dst.location().port());
}
return;
}
// Otherwise, get a set of paths that lead from here to the
// destination edge switch.
Set<Path> paths = topologyService.getPaths(topologyService.currentTopology(),
pkt.receivedFrom().deviceId(),
dst.location().deviceId());
if (paths.isEmpty()) {
// If there are no paths, flood and bail.
flood(context);
return;
}
// Otherwise, pick a path that does not lead back to where we
// came from; if no such path, flood and bail.
Path path = pickForwardPath(paths, pkt.receivedFrom().port());
if (path == null) {
log.warn("Doh... don't know where to go... {} -> {} received on {}",
ethPkt.getSourceMAC(), ethPkt.getDestinationMAC(),
pkt.receivedFrom());
flood(context);
return;
}
// Otherwise forward and be done with it.
installRule(context, path.src().port());
}
}
// Indicates whether this is a control packet, e.g. LLDP, BDDP
private boolean isControlPacket(Ethernet eth) {
short type = eth.getEtherType();
return type == Ethernet.TYPE_LLDP || type == Ethernet.TYPE_BSN;
}
// Selects a path from the given set that does not lead back to the
// specified port.
private Path pickForwardPath(Set<Path> paths, PortNumber notToPort) {
for (Path path : paths) {
if (!path.src().port().equals(notToPort)) {
return path;
}
}
return null;
}
// Floods the specified packet if permissible.
private void flood(PacketContext context) {
if (topologyService.isBroadcastPoint(topologyService.currentTopology(),
context.inPacket().receivedFrom())) {
packetOut(context, PortNumber.FLOOD);
} else {
context.block();
}
}
// Sends a packet out the specified port.
private void packetOut(PacketContext context, PortNumber portNumber) {
context.treatmentBuilder().setOutput(portNumber);
context.send();
}
// Install a rule forwarding the packet to the specified port.
private void installRule(PacketContext context, PortNumber portNumber) {
// We don't yet support bufferids in the flowservice so packet out first.
packetOut(context, portNumber);
// Install the flow rule to handle this type of message from now on.
Ethernet inPkt = context.inPacket().parsed();
TrafficSelector.Builder builder = DefaultTrafficSelector.builder();
builder.matchEthType(inPkt.getEtherType())
.matchEthSrc(inPkt.getSourceMAC())
.matchEthDst(inPkt.getDestinationMAC())
.matchInport(context.inPacket().receivedFrom().port());
TrafficTreatment.Builder treat = DefaultTrafficTreatment.builder();
treat.setOutput(portNumber);
FlowRule f = new DefaultFlowRule(context.inPacket().receivedFrom().deviceId(),
builder.build(), treat.build(), PRIORITY, appId, TIMEOUT, false);
flowRuleService.applyFlowRules(f);
}
}