2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.openflowplugin.learningswitch;
11 import java.math.BigInteger;
12 import java.nio.ByteBuffer;
13 import java.util.Arrays;
14 import java.util.HashMap;
15 import java.util.HashSet;
18 import java.util.concurrent.atomic.AtomicLong;
20 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.MacAddress;
21 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
22 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
23 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowCookie;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingListener;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInput;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInputBuilder;
40 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * Simple Learning Switch implementation which does mac learning for one switch.
49 public class LearningSwitchHandlerSimpleImpl implements LearningSwitchHandler, PacketProcessingListener {
51 private static final Logger LOG = LoggerFactory.getLogger(LearningSwitchHandler.class);
53 private static final byte[] ETH_TYPE_IPV4 = new byte[] { 0x08, 0x00 };
55 private static final int DIRECT_FLOW_PRIORITY = 512;
57 private DataChangeListenerRegistrationHolder registrationPublisher;
58 private FlowCommitWrapper dataStoreAccessor;
59 private PacketProcessingService packetProcessingService;
61 private boolean iAmLearning = false;
63 private NodeId nodeId;
64 private AtomicLong flowIdInc = new AtomicLong();
65 private AtomicLong flowCookieInc = new AtomicLong(0x2a00000000000000L);
67 private InstanceIdentifier<Node> nodePath;
68 private InstanceIdentifier<Table> tablePath;
70 private Map<MacAddress, NodeConnectorRef> mac2portMapping;
71 private Set<String> coveredMacPaths;
74 public synchronized void onSwitchAppeared(InstanceIdentifier<Table> appearedTablePath) {
76 LOG.debug("already learning a node, skipping {}", nodeId.getValue());
80 LOG.debug("expected table acquired, learning ..");
82 // disable listening - simple learning handles only one node (switch)
83 if (registrationPublisher != null) {
85 LOG.debug("closing dataChangeListenerRegistration");
86 registrationPublisher.getDataChangeListenerRegistration().close();
87 } catch (Exception e) {
88 LOG.error("closing registration upon flowCapable node update listener failed: " + e.getMessage(), e);
94 tablePath = appearedTablePath;
95 nodePath = tablePath.firstIdentifierOf(Node.class);
96 nodeId = nodePath.firstKeyOf(Node.class, NodeKey.class).getId();
97 mac2portMapping = new HashMap<>();
98 coveredMacPaths = new HashSet<>();
100 // start forwarding all packages to controller
101 FlowId flowId = new FlowId(String.valueOf(flowIdInc.getAndIncrement()));
102 FlowKey flowKey = new FlowKey(flowId);
103 InstanceIdentifier<Flow> flowPath = InstanceIdentifierUtils.createFlowPath(tablePath, flowKey);
106 // create flow in table with id = 0, priority = 4 (other params are
107 // defaulted in OFDataStoreUtil)
108 FlowBuilder allToCtrlFlow = FlowUtils.createFwdAllToControllerFlow(
109 InstanceIdentifierUtils.getTableId(tablePath), priority, flowId);
111 LOG.debug("writing packetForwardToController flow");
112 dataStoreAccessor.writeFlowToConfig(flowPath, allToCtrlFlow.build());
116 public void setRegistrationPublisher(DataChangeListenerRegistrationHolder registrationPublisher) {
117 this.registrationPublisher = registrationPublisher;
121 public void setDataStoreAccessor(FlowCommitWrapper dataStoreAccessor) {
122 this.dataStoreAccessor = dataStoreAccessor;
126 public void setPacketProcessingService(PacketProcessingService packetProcessingService) {
127 this.packetProcessingService = packetProcessingService;
131 public void onPacketReceived(PacketReceived notification) {
133 // ignoring packets - this should not happen
137 LOG.debug("Received packet via match: {}", notification.getMatch());
139 // detect and compare node - we support one switch
140 if (!nodePath.contains(notification.getIngress().getValue())) {
144 // read src MAC and dst MAC
145 byte[] dstMacRaw = PacketUtils.extractDstMac(notification.getPayload());
146 byte[] srcMacRaw = PacketUtils.extractSrcMac(notification.getPayload());
147 byte[] etherType = PacketUtils.extractEtherType(notification.getPayload());
149 MacAddress dstMac = PacketUtils.rawMacToMac(dstMacRaw);
150 MacAddress srcMac = PacketUtils.rawMacToMac(srcMacRaw);
152 NodeConnectorKey ingressKey = InstanceIdentifierUtils.getNodeConnectorKey(notification.getIngress().getValue());
154 LOG.debug("Received packet from MAC match: {}, ingress: {}", srcMac, ingressKey.getId());
155 LOG.debug("Received packet to MAC match: {}", dstMac);
156 LOG.debug("Ethertype: {}", Integer.toHexString(0x0000ffff & ByteBuffer.wrap(etherType).getShort()));
158 // learn by IPv4 traffic only
159 if (Arrays.equals(ETH_TYPE_IPV4, etherType)) {
160 NodeConnectorRef previousPort = mac2portMapping.put(srcMac, notification.getIngress());
161 if (previousPort != null && !notification.getIngress().equals(previousPort)) {
162 NodeConnectorKey previousPortKey = InstanceIdentifierUtils.getNodeConnectorKey(previousPort.getValue());
163 LOG.debug("mac2port mapping changed by mac {}: {} -> {}", srcMac, previousPortKey, ingressKey.getId());
165 // if dst MAC mapped:
166 NodeConnectorRef destNodeConnector = mac2portMapping.get(dstMac);
167 if (destNodeConnector != null) {
168 synchronized (coveredMacPaths) {
169 if (!destNodeConnector.equals(notification.getIngress())) {
171 addBridgeFlow(srcMac, dstMac, destNodeConnector);
172 addBridgeFlow(dstMac, srcMac, notification.getIngress());
174 LOG.debug("useless rule ignoring - both MACs are behind the same port");
177 LOG.debug("packetIn-directing.. to {}",
178 InstanceIdentifierUtils.getNodeConnectorKey(destNodeConnector.getValue()).getId());
179 sendPacketOut(notification.getPayload(), notification.getIngress(), destNodeConnector);
182 LOG.debug("packetIn-still flooding.. ");
183 flood(notification.getPayload(), notification.getIngress());
187 flood(notification.getPayload(), notification.getIngress());
195 * @param destNodeConnector
197 private void addBridgeFlow(MacAddress srcMac, MacAddress dstMac, NodeConnectorRef destNodeConnector) {
198 synchronized (coveredMacPaths) {
199 String macPath = srcMac.toString() + dstMac.toString();
200 if (!coveredMacPaths.contains(macPath)) {
201 LOG.debug("covering mac path: {} by [{}]", macPath,
202 destNodeConnector.getValue().firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId());
204 coveredMacPaths.add(macPath);
205 FlowId flowId = new FlowId(String.valueOf(flowIdInc.getAndIncrement()));
206 FlowKey flowKey = new FlowKey(flowId);
208 * Path to the flow we want to program.
210 InstanceIdentifier<Flow> flowPath = InstanceIdentifierUtils.createFlowPath(tablePath, flowKey);
212 Short tableId = InstanceIdentifierUtils.getTableId(tablePath);
213 FlowBuilder srcToDstFlow = FlowUtils.createDirectMacToMacFlow(tableId, DIRECT_FLOW_PRIORITY, srcMac,
214 dstMac, destNodeConnector);
215 srcToDstFlow.setCookie(new FlowCookie(BigInteger.valueOf(flowCookieInc.getAndIncrement())));
217 dataStoreAccessor.writeFlowToConfig(flowPath, srcToDstFlow.build());
222 private void flood(byte[] payload, NodeConnectorRef ingress) {
223 NodeConnectorKey nodeConnectorKey = new NodeConnectorKey(nodeConnectorId("0xfffffffb"));
224 InstanceIdentifier<?> nodeConnectorPath = InstanceIdentifierUtils.createNodeConnectorPath(nodePath, nodeConnectorKey);
225 NodeConnectorRef egressConnectorRef = new NodeConnectorRef(nodeConnectorPath);
227 sendPacketOut(payload, ingress, egressConnectorRef);
230 private NodeConnectorId nodeConnectorId(String connectorId) {
231 NodeKey nodeKey = nodePath.firstKeyOf(Node.class, NodeKey.class);
232 StringBuilder stringId = new StringBuilder(nodeKey.getId().getValue()).append(":").append(connectorId);
233 return new NodeConnectorId(stringId.toString());
236 private void sendPacketOut(byte[] payload, NodeConnectorRef ingress, NodeConnectorRef egress) {
237 InstanceIdentifier<Node> egressNodePath = InstanceIdentifierUtils.getNodePath(egress.getValue());
238 TransmitPacketInput input = new TransmitPacketInputBuilder()
240 .setNode(new NodeRef(egressNodePath))
244 packetProcessingService.transmitPacket(input);