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