Upgrade ietf-{inet,yang}-types to 2013-07-15
[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.math.BigInteger;
12 import java.nio.ByteBuffer;
13 import java.util.Arrays;
14 import java.util.HashMap;
15 import java.util.HashSet;
16 import java.util.Map;
17 import java.util.Set;
18 import java.util.concurrent.atomic.AtomicLong;
19 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.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.flow.types.rev131026.FlowCookie;
26 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorId;
27 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnectorKey;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingListener;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketReceived;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInput;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInputBuilder;
39 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44  * Simple Learning Switch implementation which does mac learning for one switch.
45  *
46  *
47  */
48 public class LearningSwitchHandlerSimpleImpl implements LearningSwitchHandler, PacketProcessingListener {
49
50     private static final Logger LOG = LoggerFactory.getLogger(LearningSwitchHandlerSimpleImpl.class);
51
52     private static final byte[] ETH_TYPE_IPV4 = new byte[] { 0x08, 0x00 };
53
54     private static final int DIRECT_FLOW_PRIORITY = 512;
55
56     private DataChangeListenerRegistrationHolder registrationPublisher;
57     private FlowCommitWrapper dataStoreAccessor;
58     private PacketProcessingService packetProcessingService;
59
60     private boolean iAmLearning = false;
61
62     private NodeId nodeId;
63     private AtomicLong flowIdInc = new AtomicLong();
64     private AtomicLong flowCookieInc = new AtomicLong(0x2a00000000000000L);
65
66     private InstanceIdentifier<Node> nodePath;
67     private InstanceIdentifier<Table> tablePath;
68
69     private Map<MacAddress, NodeConnectorRef> mac2portMapping;
70     private Set<String> coveredMacPaths;
71
72     @Override
73     public synchronized void onSwitchAppeared(InstanceIdentifier<Table> appearedTablePath) {
74         if (iAmLearning) {
75             LOG.debug("already learning a node, skipping {}", nodeId.getValue());
76             return;
77         }
78
79         LOG.debug("expected table acquired, learning ..");
80
81         // disable listening - simple learning handles only one node (switch)
82         if (registrationPublisher != null) {
83             try {
84                 LOG.debug("closing dataChangeListenerRegistration");
85                 registrationPublisher.getDataChangeListenerRegistration().close();
86             } catch (Exception e) {
87                 LOG.warn("closing registration upon flowCapable node update listener failed: {}", e.getMessage());
88                 LOG.debug("closing registration upon flowCapable node update listener failed.. ", e);
89             }
90         }
91
92         iAmLearning = true;
93
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<>();
99
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);
104
105         int priority = 0;
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);
110
111         LOG.debug("writing packetForwardToController flow");
112         dataStoreAccessor.writeFlowToConfig(flowPath, allToCtrlFlow.build());
113     }
114
115     @Override
116     public void setRegistrationPublisher(DataChangeListenerRegistrationHolder registrationPublisher) {
117         this.registrationPublisher = registrationPublisher;
118     }
119
120     @Override
121     public void setDataStoreAccessor(FlowCommitWrapper dataStoreAccessor) {
122         this.dataStoreAccessor = dataStoreAccessor;
123     }
124
125     @Override
126     public void setPacketProcessingService(PacketProcessingService packetProcessingService) {
127         this.packetProcessingService = packetProcessingService;
128     }
129
130     @Override
131     public void onPacketReceived(PacketReceived notification) {
132         if (!iAmLearning) {
133             // ignoring packets - this should not happen
134             return;
135         }
136
137         LOG.debug("Received packet via match: {}", notification.getMatch());
138
139         // detect and compare node - we support one switch
140         if (!nodePath.contains(notification.getIngress().getValue())) {
141             return;
142         }
143
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());
148
149         MacAddress dstMac = PacketUtils.rawMacToMac(dstMacRaw);
150         MacAddress srcMac = PacketUtils.rawMacToMac(srcMacRaw);
151
152         NodeConnectorKey ingressKey = InstanceIdentifierUtils.getNodeConnectorKey(notification.getIngress().getValue());
153
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()));
157
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());
164             }
165             // if dst MAC mapped:
166             NodeConnectorRef destNodeConnector = mac2portMapping.get(dstMac);
167             if (destNodeConnector != null) {
168                 synchronized (coveredMacPaths) {
169                     if (!destNodeConnector.equals(notification.getIngress())) {
170                         // add flow
171                         addBridgeFlow(srcMac, dstMac, destNodeConnector);
172                         addBridgeFlow(dstMac, srcMac, notification.getIngress());
173                     } else {
174                         LOG.debug("useless rule ignoring - both MACs are behind the same port");
175                     }
176                 }
177                 LOG.debug("packetIn-directing.. to {}",
178                         InstanceIdentifierUtils.getNodeConnectorKey(destNodeConnector.getValue()).getId());
179                 sendPacketOut(notification.getPayload(), notification.getIngress(), destNodeConnector);
180             } else {
181                 // flood
182                 LOG.debug("packetIn-still flooding.. ");
183                 flood(notification.getPayload(), notification.getIngress());
184             }
185         } else {
186             // non IPv4 package
187             flood(notification.getPayload(), notification.getIngress());
188         }
189
190     }
191
192     /**
193      * @param srcMac
194      * @param dstMac
195      * @param destNodeConnector
196      */
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());
203
204                 coveredMacPaths.add(macPath);
205                 FlowId flowId = new FlowId(String.valueOf(flowIdInc.getAndIncrement()));
206                 FlowKey flowKey = new FlowKey(flowId);
207                 /**
208                  * Path to the flow we want to program.
209                  */
210                 InstanceIdentifier<Flow> flowPath = InstanceIdentifierUtils.createFlowPath(tablePath, flowKey);
211
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())));
216
217                 dataStoreAccessor.writeFlowToConfig(flowPath, srcToDstFlow.build());
218             }
219         }
220     }
221
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);
226
227         sendPacketOut(payload, ingress, egressConnectorRef);
228     }
229
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());
234     }
235
236     private void sendPacketOut(byte[] payload, NodeConnectorRef ingress, NodeConnectorRef egress) {
237         InstanceIdentifier<Node> egressNodePath = InstanceIdentifierUtils.getNodePath(egress.getValue());
238         TransmitPacketInput input = new TransmitPacketInputBuilder()
239                 .setPayload(payload)
240                 .setNode(new NodeRef(egressNodePath))
241                 .setEgress(egress)
242                 .setIngress(ingress)
243                 .build();
244         packetProcessingService.transmitPacket(input);
245     }
246 }