Adding multipart learning switch
[openflowplugin.git] / samples / learning-switch / src / main / java / org / opendaylight / openflowplugin / learningswitch / LearningSwitchHandlerSimpleImpl.java
1 /**
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.openflowplugin.learningswitch;
10
11 import java.nio.ByteBuffer;
12 import java.util.Arrays;
13 import java.util.HashMap;
14 import java.util.HashSet;
15 import java.util.Map;
16 import java.util.Set;
17 import java.util.concurrent.atomic.AtomicLong;
18
19 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev100924.MacAddress;
20 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
21 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
22 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
23 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowBuilder;
24 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey;
25 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingListener;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInput;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInputBuilder;
38 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42 /**
43  * Simple Learning Switch implementation which does mac learning for one switch.
44  * 
45  * 
46  */
47 public class LearningSwitchHandlerSimpleImpl implements LearningSwitchHandler, PacketProcessingListener {
48
49     private static final Logger LOG = LoggerFactory.getLogger(LearningSwitchHandler.class);
50
51     private static final byte[] ETH_TYPE_IPV4 = new byte[] { 0x08, 0x00 };
52
53     private static final int DIRECT_FLOW_PRIORITY = 512;
54
55     private DataChangeListenerRegistrationHolder registrationPublisher;
56     private FlowCommitWrapper dataStoreAccessor;
57     private PacketProcessingService packetProcessingService;
58
59     private boolean iAmLearning = false;
60
61     private NodeId nodeId;
62     private AtomicLong flowIdInc = new AtomicLong();
63     private InstanceIdentifier<Node> nodePath;
64     private InstanceIdentifier<Table> tablePath;
65
66     private Map<MacAddress, NodeConnectorRef> mac2portMapping;
67     private Set<String> coveredMacPaths;
68
69     @Override
70     public synchronized void onSwitchAppeared(InstanceIdentifier<Table> appearedTablePath) {
71         if (iAmLearning) {
72             LOG.debug("already learning a node, skipping {}", nodeId.getValue());
73             return;
74         }
75
76         LOG.debug("expected table acquired, learning ..");
77
78         // disable listening - simple learning handles only one node (switch)
79         if (registrationPublisher != null) {
80             try {
81                 LOG.debug("closing dataChangeListenerRegistration");
82                 registrationPublisher.getDataChangeListenerRegistration().close();
83             } catch (Exception e) {
84                 LOG.error("closing registration upon flowCapable node update listener failed: " + e.getMessage(), e);
85             }
86         }
87
88         iAmLearning = true;
89         
90         tablePath = appearedTablePath;
91         nodePath = tablePath.firstIdentifierOf(Node.class);
92         nodeId = nodePath.firstKeyOf(Node.class, NodeKey.class).getId();
93         mac2portMapping = new HashMap<>();
94         coveredMacPaths = new HashSet<>();
95
96         // start forwarding all packages to controller
97         FlowId flowId = new FlowId(String.valueOf(flowIdInc.getAndIncrement()));
98         FlowKey flowKey = new FlowKey(flowId);
99         InstanceIdentifier<Flow> flowPath = InstanceIdentifierUtils.createFlowPath(tablePath, flowKey);
100
101         int priority = 0;
102         // create flow in table with id = 0, priority = 4 (other params are
103         // defaulted in OFDataStoreUtil)
104         FlowBuilder allToCtrlFlow = FlowUtils.createFwdAllToControllerFlow(
105                 InstanceIdentifierUtils.getTableId(tablePath), priority, flowId);
106
107         LOG.debug("writing packetForwardToController flow");
108         dataStoreAccessor.writeFlowToConfig(flowPath, allToCtrlFlow.build());
109     }
110
111     @Override
112     public void setRegistrationPublisher(DataChangeListenerRegistrationHolder registrationPublisher) {
113         this.registrationPublisher = registrationPublisher;
114     }
115
116     @Override
117     public void setDataStoreAccessor(FlowCommitWrapper dataStoreAccessor) {
118         this.dataStoreAccessor = dataStoreAccessor;
119     }
120
121     @Override
122     public void setPacketProcessingService(PacketProcessingService packetProcessingService) {
123         this.packetProcessingService = packetProcessingService;
124     }
125
126     @Override
127     public void onPacketReceived(PacketReceived notification) {
128         if (!iAmLearning) {
129             // ignoring packets - this should not happen
130             return;
131         }
132
133         LOG.debug("Received packet via match: {}", notification.getMatch());
134
135         // detect and compare node - we support one switch
136         if (!nodePath.contains(notification.getIngress().getValue())) {
137             return;
138         }
139
140         // read src MAC and dst MAC
141         byte[] dstMacRaw = PacketUtils.extractDstMac(notification.getPayload());
142         byte[] srcMacRaw = PacketUtils.extractSrcMac(notification.getPayload());
143         byte[] etherType = PacketUtils.extractEtherType(notification.getPayload());
144
145         MacAddress dstMac = PacketUtils.rawMacToMac(dstMacRaw);
146         MacAddress srcMac = PacketUtils.rawMacToMac(srcMacRaw);
147
148         NodeConnectorKey ingressKey = InstanceIdentifierUtils.getNodeConnectorKey(notification.getIngress().getValue());
149
150         LOG.debug("Received packet from MAC match: {}, ingress: {}", srcMac, ingressKey.getId());
151         LOG.debug("Received packet to   MAC match: {}", dstMac);
152         LOG.debug("Ethertype: {}", Integer.toHexString(0x0000ffff & ByteBuffer.wrap(etherType).getShort()));
153
154         // learn by IPv4 traffic only
155         if (Arrays.equals(ETH_TYPE_IPV4, etherType)) {
156             NodeConnectorRef previousPort = mac2portMapping.put(srcMac, notification.getIngress());
157             if (previousPort != null && !notification.getIngress().equals(previousPort)) {
158                 NodeConnectorKey previousPortKey = InstanceIdentifierUtils.getNodeConnectorKey(previousPort.getValue());
159                 LOG.debug("mac2port mapping changed by mac {}: {} -> {}", srcMac, previousPortKey, ingressKey.getId());
160             }
161             // if dst MAC mapped:
162             NodeConnectorRef destNodeConnector = mac2portMapping.get(dstMac);
163             if (destNodeConnector != null) {
164                 synchronized (coveredMacPaths) {
165                     if (!destNodeConnector.equals(notification.getIngress())) {
166                         // add flow
167                         addBridgeFlow(srcMac, dstMac, destNodeConnector);
168                         addBridgeFlow(dstMac, srcMac, notification.getIngress());
169                     } else {
170                         LOG.debug("useless rule ignoring - both MACs are behind the same port");
171                     }
172                 }
173                 LOG.debug("packetIn-directing.. to {}",
174                         InstanceIdentifierUtils.getNodeConnectorKey(destNodeConnector.getValue()).getId());
175                 sendPacketOut(notification.getPayload(), notification.getIngress(), destNodeConnector);
176             } else {
177                 // flood
178                 LOG.debug("packetIn-still flooding.. ");
179                 flood(notification.getPayload(), notification.getIngress());
180             }
181         } else {
182             // non IPv4 package
183             flood(notification.getPayload(), notification.getIngress());
184         }
185
186     }
187
188     /**
189      * @param srcMac
190      * @param dstMac
191      * @param destNodeConnector
192      */
193     private void addBridgeFlow(MacAddress srcMac, MacAddress dstMac, NodeConnectorRef destNodeConnector) {
194         synchronized (coveredMacPaths) {
195             String macPath = srcMac.toString() + dstMac.toString();
196             if (!coveredMacPaths.contains(macPath)) {
197                 LOG.debug("covering mac path: {} by [{}]", macPath,
198                         destNodeConnector.getValue().firstKeyOf(NodeConnector.class, NodeConnectorKey.class).getId());
199
200                 coveredMacPaths.add(macPath);
201                 FlowId flowId = new FlowId(String.valueOf(flowIdInc.getAndIncrement()));
202                 FlowKey flowKey = new FlowKey(flowId);
203                 /**
204                  * Path to the flow we want to program.
205                  */
206                 InstanceIdentifier<Flow> flowPath = InstanceIdentifierUtils.createFlowPath(tablePath, flowKey);
207
208                 Short tableId = InstanceIdentifierUtils.getTableId(tablePath);
209                 FlowBuilder srcToDstFlow = FlowUtils.createDirectMacToMacFlow(tableId, DIRECT_FLOW_PRIORITY, srcMac,
210                         dstMac, destNodeConnector);
211
212                 dataStoreAccessor.writeFlowToConfig(flowPath, srcToDstFlow.build());
213             }
214         }
215     }
216
217     private void flood(byte[] payload, NodeConnectorRef ingress) {
218         NodeConnectorKey nodeConnectorKey = new NodeConnectorKey(nodeConnectorId("0xfffffffb"));
219         InstanceIdentifier<?> nodeConnectorPath = InstanceIdentifierUtils.createNodeConnectorPath(nodePath, nodeConnectorKey);
220         NodeConnectorRef egressConnectorRef = new NodeConnectorRef(nodeConnectorPath);
221
222         sendPacketOut(payload, ingress, egressConnectorRef);
223     }
224
225     private NodeConnectorId nodeConnectorId(String connectorId) {
226         NodeKey nodeKey = nodePath.firstKeyOf(Node.class, NodeKey.class);
227         StringBuilder stringId = new StringBuilder(nodeKey.getId().getValue()).append(":").append(connectorId);
228         return new NodeConnectorId(stringId.toString());
229     }
230
231     private void sendPacketOut(byte[] payload, NodeConnectorRef ingress, NodeConnectorRef egress) {
232         InstanceIdentifier<Node> egressNodePath = InstanceIdentifierUtils.getNodePath(egress.getValue());
233         TransmitPacketInput input = new TransmitPacketInputBuilder() //
234                 .setPayload(payload) //
235                 .setNode(new NodeRef(egressNodePath)) //
236                 .setEgress(egress) //
237                 .setIngress(ingress) //
238                 .build();
239         packetProcessingService.transmitPacket(input);
240     }
241 }