blob: 2f0d5d87e0593b65cafaabf9b0f98acc40b6dce2 [file] [log] [blame]
alshabibdf652ad2014-09-09 11:53:19 -07001/*******************************************************************************
2 * Copyright 2014 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 ******************************************************************************/
16package org.onlab.onos.provider.of.link.impl;
17
18import static org.slf4j.LoggerFactory.getLogger;
19
20import java.util.Collections;
21import java.util.HashMap;
22import java.util.HashSet;
23import java.util.Iterator;
alshabibdf652ad2014-09-09 11:53:19 -070024import java.util.Map;
25import java.util.Set;
alshabib9ee68172014-09-09 14:45:14 -070026import java.util.concurrent.ConcurrentHashMap;
alshabibdf652ad2014-09-09 11:53:19 -070027import java.util.concurrent.TimeUnit;
28import java.util.concurrent.atomic.AtomicInteger;
29
30import org.jboss.netty.util.Timeout;
31import org.jboss.netty.util.TimerTask;
32import org.onlab.onos.net.ConnectPoint;
33import org.onlab.onos.net.DeviceId;
34import org.onlab.onos.net.Link.Type;
35import org.onlab.onos.net.PortNumber;
36import org.onlab.onos.net.link.DefaultLinkDescription;
37import org.onlab.onos.net.link.LinkDescription;
38import org.onlab.onos.net.link.LinkProviderService;
39import org.onlab.onos.of.controller.Dpid;
40import org.onlab.onos.of.controller.OpenFlowController;
41import org.onlab.onos.of.controller.OpenFlowSwitch;
42import org.onlab.packet.Ethernet;
43import org.onlab.packet.ONLabLddp;
44import org.onlab.packet.ONLabLddp.DPIDandPort;
45import org.onlab.timer.Timer;
46import org.projectfloodlight.openflow.protocol.OFFactory;
47import org.projectfloodlight.openflow.protocol.OFMessage;
48import org.projectfloodlight.openflow.protocol.OFPacketOut;
49import org.projectfloodlight.openflow.protocol.OFPortDesc;
50import org.projectfloodlight.openflow.protocol.action.OFAction;
51import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
52import org.projectfloodlight.openflow.types.OFBufferId;
53import org.projectfloodlight.openflow.types.OFPort;
54import org.slf4j.Logger;
55
56
57
58/**
59 * Run discovery process from a physical switch. Ports are initially labeled as
60 * slow ports. When an LLDP is successfully received, label the remote port as
61 * fast. Every probeRate milliseconds, loop over all fast ports and send an
62 * LLDP, send an LLDP for a single slow port. Based on FlowVisor topology
63 * discovery implementation.
64 *
65 * TODO: add 'fast discovery' mode: drop LLDPs in destination switch but listen
66 * for flow_removed messages
67 */
68public class LinkDiscovery implements TimerTask {
69
70 private final OpenFlowSwitch sw;
71 // send 1 probe every probeRate milliseconds
72 private final long probeRate;
73 private final Set<Integer> slowPorts;
74 private final Set<Integer> fastPorts;
75 // number of unacknowledged probes per port
76 private final Map<Integer, AtomicInteger> portProbeCount;
77 // number of probes to send before link is removed
78 private static final short MAX_PROBE_COUNT = 3;
79 private Iterator<Integer> slowIterator;
80 private final OFFactory ofFactory;
81 private final Logger log = getLogger(getClass());
82 private final ONLabLddp lldpPacket;
83 private final Ethernet ethPacket;
84 private Ethernet bddpEth;
85 private final boolean useBDDP;
86 private final OpenFlowController ctrl;
87 private final LinkProviderService linkProvider;
alshabib9ee68172014-09-09 14:45:14 -070088 private final Map<Integer, OFPortDesc> ports;
alshabibdf652ad2014-09-09 11:53:19 -070089
90 /**
91 * Instantiates discovery manager for the given physical switch. Creates a
92 * generic LLDP packet that will be customized for the port it is sent out on.
93 * Starts the the timer for the discovery process.
94 *
95 * @param sw the physical switch
96 * @param useBDDP flag to also use BDDP for discovery
97 */
98 public LinkDiscovery(final OpenFlowSwitch sw,
99 OpenFlowController ctrl, LinkProviderService providerService, Boolean... useBDDP) {
100 this.sw = sw;
101 this.ofFactory = sw.factory();
102 this.ctrl = ctrl;
103 this.probeRate = 1000;
104 this.linkProvider = providerService;
105 this.slowPorts = Collections.synchronizedSet(new HashSet<Integer>());
106 this.fastPorts = Collections.synchronizedSet(new HashSet<Integer>());
alshabib9ee68172014-09-09 14:45:14 -0700107 this.ports = new ConcurrentHashMap<>();
alshabibdf652ad2014-09-09 11:53:19 -0700108 this.portProbeCount = new HashMap<Integer, AtomicInteger>();
109 this.lldpPacket = new ONLabLddp();
110 this.lldpPacket.setSwitch(this.sw.getId());
111 this.ethPacket = new Ethernet();
112 this.ethPacket.setEtherType(Ethernet.TYPE_LLDP);
113 this.ethPacket.setDestinationMACAddress(ONLabLddp.LLDP_NICIRA);
114 this.ethPacket.setPayload(this.lldpPacket);
115 this.ethPacket.setPad(true);
116 this.useBDDP = useBDDP.length > 0 ? useBDDP[0] : false;
117 if (this.useBDDP) {
118 this.bddpEth = new Ethernet();
119 this.bddpEth.setPayload(this.lldpPacket);
120 this.bddpEth.setEtherType(Ethernet.TYPE_BSN);
121 this.bddpEth.setDestinationMACAddress(ONLabLddp.BDDP_MULTICAST);
122 this.bddpEth.setPad(true);
123 log.info("Using BDDP to discover network");
124 }
125 for (OFPortDesc port : sw.getPorts()) {
126 if (port.getPortNo() != OFPort.LOCAL) {
127 addPort(port);
128 }
129 }
130 Timer.getTimer().newTimeout(this, this.probeRate,
131 TimeUnit.MILLISECONDS);
132 this.log.debug("Started discovery manager for switch {}",
133 sw.getId());
134
135 }
136
137 /**
138 * Add physical port port to discovery process.
139 * Send out initial LLDP and label it as slow port.
140 *
141 * @param port the port
142 */
143 public void addPort(final OFPortDesc port) {
144 // Ignore ports that are not on this switch, or already booted. */
alshabib9ee68172014-09-09 14:45:14 -0700145 this.ports.put(port.getPortNo().getPortNumber(), port);
alshabibdf652ad2014-09-09 11:53:19 -0700146 synchronized (this) {
147 this.log.debug("sending init probe to port {}",
148 port.getPortNo().getPortNumber());
149 OFPacketOut pkt;
150
151 pkt = this.createLLDPPacketOut(port);
152 this.sw.sendMsg(pkt);
153 if (useBDDP) {
154 OFPacketOut bpkt = this.createBDDPPacketOut(port);
155 this.sw.sendMsg(bpkt);
156 }
157
158 this.slowPorts.add(port.getPortNo().getPortNumber());
159 this.slowIterator = this.slowPorts.iterator();
160 }
161
162 }
163
164 /**
165 * Removes physical port from discovery process.
166 *
167 * @param port the port
168 */
alshabiba159a322014-09-09 14:50:51 -0700169 public void removePort(final OFPortDesc port) {
alshabibdf652ad2014-09-09 11:53:19 -0700170 // Ignore ports that are not on this switch
171
alshabiba159a322014-09-09 14:50:51 -0700172 int portnum = port.getPortNo().getPortNumber();
173 this.ports.remove(portnum);
alshabibdf652ad2014-09-09 11:53:19 -0700174 synchronized (this) {
175 if (this.slowPorts.contains(portnum)) {
176 this.slowPorts.remove(portnum);
177 this.slowIterator = this.slowPorts.iterator();
178
179 } else if (this.fastPorts.contains(portnum)) {
180 this.fastPorts.remove(portnum);
181 this.portProbeCount.remove(portnum);
182 // no iterator to update
183 } else {
184 this.log.warn(
185 "tried to dynamically remove non-existing port {}",
186 portnum);
187 }
188 }
189
190 }
191
192 /**
193 * Method called by remote port to acknowledge receipt of LLDP sent by
194 * this port. If slow port, updates label to fast. If fast port, decrements
195 * number of unacknowledged probes.
196 *
197 * @param port the port
198 */
199 public void ackProbe(final Integer port) {
200 final int portNumber = port;
201 synchronized (this) {
202 if (this.slowPorts.contains(portNumber)) {
203 this.log.debug("Setting slow port to fast: {}:{}",
204 this.sw.getId(), portNumber);
205 this.slowPorts.remove(portNumber);
206 this.slowIterator = this.slowPorts.iterator();
207 this.fastPorts.add(portNumber);
208 this.portProbeCount.put(portNumber, new AtomicInteger(0));
209 } else {
210 if (this.fastPorts.contains(portNumber)) {
211 this.portProbeCount.get(portNumber).set(0);
212 } else {
213 this.log.debug(
214 "Got ackProbe for non-existing port: {}",
215 portNumber);
216 }
217 }
218 }
219 }
220
221 /**
222 * Creates packet_out LLDP for specified output port.
223 *
224 * @param port the port
225 * @return Packet_out message with LLDP data
226 * @throws PortMappingException
227 */
228 private OFPacketOut createLLDPPacketOut(final OFPortDesc port) {
229 OFPacketOut.Builder packetOut = this.ofFactory.buildPacketOut();
230 packetOut.setBufferId(OFBufferId.NO_BUFFER);
231 OFAction act = this.ofFactory.actions().buildOutput()
232 .setPort(port.getPortNo()).build();
233 packetOut.setActions(Collections.singletonList(act));
234 this.lldpPacket.setPort(port.getPortNo().getPortNumber());
235 this.ethPacket.setSourceMACAddress(port.getHwAddr().getBytes());
236
237 final byte[] lldp = this.ethPacket.serialize();
238 packetOut.setData(lldp);
239 return packetOut.build();
240 }
241
242 /**
243 * Creates packet_out BDDP for specified output port.
244 *
245 * @param port the port
246 * @return Packet_out message with LLDP data
247 * @throws PortMappingException
248 */
249 private OFPacketOut createBDDPPacketOut(final OFPortDesc port) {
250 OFPacketOut.Builder packetOut = sw.factory().buildPacketOut();
251
252 packetOut.setBufferId(OFBufferId.NO_BUFFER);
253
254 OFActionOutput.Builder act = sw.factory().actions().buildOutput()
255 .setPort(port.getPortNo());
256 OFAction out = act.build();
257 packetOut.setActions(Collections.singletonList(out));
258 this.lldpPacket.setPort(port.getPortNo().getPortNumber());
259 this.bddpEth.setSourceMACAddress(port.getHwAddr().getBytes());
260
261 final byte[] bddp = this.bddpEth.serialize();
262 packetOut.setData(bddp);
263
264 return packetOut.build();
265 }
266
267
268 private void sendMsg(final OFMessage msg) {
269 this.sw.sendMsg(msg);
270 }
271
272 public String getName() {
273 return "LinkDiscovery " + this.sw.getStringId();
274 }
275
276 /*
277 * Handles an incoming LLDP packet. Creates link in topology and sends ACK
278 * to port where LLDP originated.
279 */
alshabib9ee68172014-09-09 14:45:14 -0700280 public void handleLLDP(final byte[] pkt, Integer inPort) {
alshabibdf652ad2014-09-09 11:53:19 -0700281
alshabib9ee68172014-09-09 14:45:14 -0700282 short ethType = ONLabLddp.isOVXLLDP(pkt);
283 if (ethType == Ethernet.TYPE_LLDP || ethType == Ethernet.TYPE_BSN) {
alshabibdf652ad2014-09-09 11:53:19 -0700284 final Integer dstPort = inPort;
285 final DPIDandPort dp = ONLabLddp.parseLLDP(pkt);
286 final OpenFlowSwitch srcSwitch = ctrl.getSwitch(new Dpid(dp.getDpid()));
287 final Integer srcPort = dp.getPort();
288 if (srcSwitch == null) {
289 return;
290 }
291 this.ackProbe(srcPort);
292 ConnectPoint src = new ConnectPoint(
293 DeviceId.deviceId("of:" + Long.toHexString(srcSwitch.getId())),
294 PortNumber.portNumber(srcPort));
295
296 ConnectPoint dst = new ConnectPoint(
297 DeviceId.deviceId("of:" + Long.toHexString(sw.getId())),
298 PortNumber.portNumber(dstPort));
299 LinkDescription ld;
alshabib9ee68172014-09-09 14:45:14 -0700300 if (ethType == Ethernet.TYPE_BSN) {
alshabibdf652ad2014-09-09 11:53:19 -0700301 ld = new DefaultLinkDescription(src, dst, Type.INDIRECT);
302 } else {
303 ld = new DefaultLinkDescription(src, dst, Type.DIRECT);
304 }
305 linkProvider.linkDetected(ld);
306 } else {
307 this.log.debug("Ignoring unknown LLDP");
308 }
309 }
310
alshabib9ee68172014-09-09 14:45:14 -0700311 private OFPortDesc findPort(Integer inPort) {
312 return ports.get(inPort);
alshabibdf652ad2014-09-09 11:53:19 -0700313 }
314
315 /**
316 * Execute this method every t milliseconds. Loops over all ports
317 * labeled as fast and sends out an LLDP. Send out an LLDP on a single slow
318 * port.
319 *
320 * @param t timeout
321 * @throws Exception
322 */
323 @Override
324 public void run(final Timeout t) {
325 this.log.debug("sending probes");
326 synchronized (this) {
327 final Iterator<Integer> fastIterator = this.fastPorts.iterator();
328 while (fastIterator.hasNext()) {
329 final Integer portNumber = fastIterator.next();
330 final int probeCount = this.portProbeCount.get(portNumber)
331 .getAndIncrement();
alshabib9ee68172014-09-09 14:45:14 -0700332 OFPortDesc port = findPort(portNumber);
alshabibdf652ad2014-09-09 11:53:19 -0700333 if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) {
334 this.log.debug("sending fast probe to port");
335
336 OFPacketOut pkt = this.createLLDPPacketOut(port);
337 this.sendMsg(pkt);
338 if (useBDDP) {
339 OFPacketOut bpkt = this.createBDDPPacketOut(port);
340 this.sendMsg(bpkt);
341 }
342 } else {
343 // Update fast and slow ports
344 fastIterator.remove();
345 this.slowPorts.add(portNumber);
346 this.slowIterator = this.slowPorts.iterator();
347 this.portProbeCount.remove(portNumber);
348
349 // Remove link from topology
350 final OFPortDesc srcPort = port;
351
352 ConnectPoint cp = new ConnectPoint(
353 DeviceId.deviceId("of:" + Long.toHexString(sw.getId())),
354 PortNumber.portNumber(srcPort.getPortNo().getPortNumber()));
355 linkProvider.linksVanished(cp);
356 }
357 }
358
359 // send a probe for the next slow port
360 if (this.slowPorts.size() > 0) {
361 if (!this.slowIterator.hasNext()) {
362 this.slowIterator = this.slowPorts.iterator();
363 }
364 if (this.slowIterator.hasNext()) {
365 final int portNumber = this.slowIterator.next();
366 this.log.debug("sending slow probe to port {}", portNumber);
alshabib9ee68172014-09-09 14:45:14 -0700367 OFPortDesc port = findPort(portNumber);
alshabibdf652ad2014-09-09 11:53:19 -0700368
369 OFPacketOut pkt = this.createLLDPPacketOut(port);
370 this.sendMsg(pkt);
371 if (useBDDP) {
372 OFPacketOut bpkt = this.createBDDPPacketOut(port);
373 this.sendMsg(bpkt);
374 }
375
376 }
377 }
378 }
379
380 // reschedule timer
381 Timer.getTimer().newTimeout(this, this.probeRate,
382 TimeUnit.MILLISECONDS);
383 }
384
385 public void removeAllPorts() {
386 for (OFPortDesc port : sw.getPorts()) {
alshabiba159a322014-09-09 14:50:51 -0700387 removePort(port);
alshabibdf652ad2014-09-09 11:53:19 -0700388 }
389 }
390
391}