blob: ce6d4fbc746fa0e226059d5f055dff7fa745d61e [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07002 * Copyright 2014 Open Networking Laboratory
alshabib7911a052014-10-16 17:49:37 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * 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
alshabib7911a052014-10-16 17:49:37 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * 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.
Thomas Vachuska781d18b2014-10-27 10:31:25 -070015 */
alshabib7911a052014-10-16 17:49:37 -070016package org.onlab.onos.provider.lldp.impl;
17
Jonathan Hart4f1ba092014-10-29 17:50:48 -070018import static com.google.common.base.Preconditions.checkNotNull;
19import static java.util.concurrent.TimeUnit.MILLISECONDS;
20import static org.onlab.onos.net.MastershipRole.MASTER;
21import static org.onlab.onos.net.PortNumber.portNumber;
22import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
23import static org.slf4j.LoggerFactory.getLogger;
24
25import java.nio.ByteBuffer;
26import java.util.Collections;
27import java.util.HashMap;
28import java.util.HashSet;
29import java.util.Iterator;
30import java.util.Map;
31import java.util.Set;
32import java.util.concurrent.atomic.AtomicInteger;
33
alshabib7911a052014-10-16 17:49:37 -070034import org.jboss.netty.util.Timeout;
35import org.jboss.netty.util.TimerTask;
alshabib875d6262014-10-17 16:19:40 -070036import org.onlab.onos.mastership.MastershipService;
alshabib7911a052014-10-16 17:49:37 -070037import org.onlab.onos.net.ConnectPoint;
38import org.onlab.onos.net.Device;
39import org.onlab.onos.net.DeviceId;
40import org.onlab.onos.net.Link.Type;
41import org.onlab.onos.net.Port;
42import org.onlab.onos.net.PortNumber;
alshabib7911a052014-10-16 17:49:37 -070043import org.onlab.onos.net.link.DefaultLinkDescription;
44import org.onlab.onos.net.link.LinkDescription;
45import org.onlab.onos.net.link.LinkProviderService;
46import org.onlab.onos.net.packet.DefaultOutboundPacket;
47import org.onlab.onos.net.packet.OutboundPacket;
48import org.onlab.onos.net.packet.PacketContext;
49import org.onlab.onos.net.packet.PacketService;
50import org.onlab.packet.Ethernet;
51import org.onlab.packet.ONOSLLDP;
52import org.onlab.util.Timer;
53import org.slf4j.Logger;
54
alshabib7911a052014-10-16 17:49:37 -070055/**
56 * Run discovery process from a physical switch. Ports are initially labeled as
57 * slow ports. When an LLDP is successfully received, label the remote port as
58 * fast. Every probeRate milliseconds, loop over all fast ports and send an
59 * LLDP, send an LLDP for a single slow port. Based on FlowVisor topology
60 * discovery implementation.
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -070061 * <p/>
alshabib7911a052014-10-16 17:49:37 -070062 * TODO: add 'fast discovery' mode: drop LLDPs in destination switch but listen
63 * for flow_removed messages
64 */
65public class LinkDiscovery implements TimerTask {
66
67 private final Device device;
68 // send 1 probe every probeRate milliseconds
69 private final long probeRate;
70 private final Set<Long> slowPorts;
Yuta HIGUCHIf6725882014-10-29 15:25:51 -070071 // ports, known to have incoming links
alshabib7911a052014-10-16 17:49:37 -070072 private final Set<Long> fastPorts;
73 // number of unacknowledged probes per port
74 private final Map<Long, AtomicInteger> portProbeCount;
75 // number of probes to send before link is removed
76 private static final short MAX_PROBE_COUNT = 3;
77 private final Logger log = getLogger(getClass());
78 private final ONOSLLDP lldpPacket;
79 private final Ethernet ethPacket;
80 private Ethernet bddpEth;
81 private final boolean useBDDP;
82 private final LinkProviderService linkProvider;
83 private final PacketService pktService;
alshabib875d6262014-10-17 16:19:40 -070084 private final MastershipService mastershipService;
alshabib7911a052014-10-16 17:49:37 -070085 private Timeout timeout;
alshabib0ed6a202014-10-19 12:42:57 -070086 private boolean isStopped;
alshabib7911a052014-10-16 17:49:37 -070087
88 /**
89 * Instantiates discovery manager for the given physical switch. Creates a
90 * generic LLDP packet that will be customized for the port it is sent out on.
91 * Starts the the timer for the discovery process.
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -070092 *
93 * @param device the physical switch
94 * @param masterService mastership service
95 * @param useBDDP flag to also use BDDP for discovery
alshabib7911a052014-10-16 17:49:37 -070096 */
97 public LinkDiscovery(Device device, PacketService pktService,
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -070098 MastershipService masterService,
99 LinkProviderService providerService, Boolean... useBDDP) {
alshabib7911a052014-10-16 17:49:37 -0700100 this.device = device;
101 this.probeRate = 3000;
102 this.linkProvider = providerService;
103 this.pktService = pktService;
alshabib3d643ec2014-10-22 18:33:00 -0700104
105 this.mastershipService = checkNotNull(masterService, "WTF!");
alshabib7911a052014-10-16 17:49:37 -0700106 this.slowPorts = Collections.synchronizedSet(new HashSet<Long>());
107 this.fastPorts = Collections.synchronizedSet(new HashSet<Long>());
108 this.portProbeCount = new HashMap<>();
109 this.lldpPacket = new ONOSLLDP();
110 this.lldpPacket.setChassisId(device.chassisId());
111 this.lldpPacket.setDevice(device.id().toString());
112
113
114 this.ethPacket = new Ethernet();
115 this.ethPacket.setEtherType(Ethernet.TYPE_LLDP);
116 this.ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA);
117 this.ethPacket.setPayload(this.lldpPacket);
118 this.ethPacket.setPad(true);
119 this.useBDDP = useBDDP.length > 0 ? useBDDP[0] : false;
120 if (this.useBDDP) {
121 this.bddpEth = new Ethernet();
122 this.bddpEth.setPayload(this.lldpPacket);
123 this.bddpEth.setEtherType(Ethernet.TYPE_BSN);
124 this.bddpEth.setDestinationMACAddress(ONOSLLDP.BDDP_MULTICAST);
125 this.bddpEth.setPad(true);
126 log.info("Using BDDP to discover network");
127 }
128
Yuta HIGUCHIf6725882014-10-29 15:25:51 -0700129 this.isStopped = true;
alshabib7911a052014-10-16 17:49:37 -0700130 start();
131 this.log.debug("Started discovery manager for switch {}",
132 device.id());
133
134 }
135
136 /**
137 * Add physical port port to discovery process.
138 * Send out initial LLDP and label it as slow port.
139 *
140 * @param port the port
141 */
142 public void addPort(final Port port) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700143 this.log.debug("Sending init probe to port {}@{}",
144 port.number().toLong(), device.id());
Yuta HIGUCHIf6725882014-10-29 15:25:51 -0700145 boolean isMaster = mastershipService.getLocalRole(device.id()) == MASTER;
146 if (isMaster) {
147 sendProbes(port.number().toLong());
148 }
alshabib7911a052014-10-16 17:49:37 -0700149 synchronized (this) {
150 this.slowPorts.add(port.number().toLong());
151 }
alshabib7911a052014-10-16 17:49:37 -0700152 }
153
154 /**
155 * Removes physical port from discovery process.
156 *
157 * @param port the port
158 */
159 public void removePort(final Port port) {
160 // Ignore ports that are not on this switch
161
162 long portnum = port.number().toLong();
163 synchronized (this) {
164 if (this.slowPorts.contains(portnum)) {
165 this.slowPorts.remove(portnum);
166
167 } else if (this.fastPorts.contains(portnum)) {
168 this.fastPorts.remove(portnum);
169 this.portProbeCount.remove(portnum);
170 // no iterator to update
171 } else {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700172 this.log.warn("Tried to dynamically remove non-existing port {}",
173 portnum);
alshabib7911a052014-10-16 17:49:37 -0700174 }
175 }
176 }
177
178 /**
179 * Method called by remote port to acknowledge receipt of LLDP sent by
180 * this port. If slow port, updates label to fast. If fast port, decrements
181 * number of unacknowledged probes.
182 *
183 * @param portNumber the port
184 */
185 public void ackProbe(final Long portNumber) {
alshabib7911a052014-10-16 17:49:37 -0700186 synchronized (this) {
187 if (this.slowPorts.contains(portNumber)) {
188 this.log.debug("Setting slow port to fast: {}:{}",
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700189 this.device.id(), portNumber);
alshabib7911a052014-10-16 17:49:37 -0700190 this.slowPorts.remove(portNumber);
191 this.fastPorts.add(portNumber);
192 this.portProbeCount.put(portNumber, new AtomicInteger(0));
alshabibacd91832014-10-17 14:38:41 -0700193 } else if (this.fastPorts.contains(portNumber)) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700194 this.portProbeCount.get(portNumber).set(0);
alshabibacd91832014-10-17 14:38:41 -0700195 } else {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700196 this.log.debug("Got ackProbe for non-existing port: {}", portNumber);
alshabib7911a052014-10-16 17:49:37 -0700197 }
198 }
199 }
200
201
202 /**
203 * Handles an incoming LLDP packet. Creates link in topology and sends ACK
204 * to port where LLDP originated.
205 */
206 public boolean handleLLDP(PacketContext context) {
207 Ethernet eth = context.inPacket().parsed();
208 ONOSLLDP onoslldp = ONOSLLDP.parseONOSLLDP(eth);
209 if (onoslldp != null) {
210 final PortNumber dstPort =
211 context.inPacket().receivedFrom().port();
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700212 final PortNumber srcPort = portNumber(onoslldp.getPort());
alshabib7911a052014-10-16 17:49:37 -0700213 final DeviceId srcDeviceId = DeviceId.deviceId(onoslldp.getDeviceString());
214 final DeviceId dstDeviceId = context.inPacket().receivedFrom().deviceId();
Jonathan Hart43ef46f2014-10-23 08:33:33 -0700215 this.ackProbe(dstPort.toLong());
alshabib7911a052014-10-16 17:49:37 -0700216 ConnectPoint src = new ConnectPoint(srcDeviceId, srcPort);
217 ConnectPoint dst = new ConnectPoint(dstDeviceId, dstPort);
218
219 LinkDescription ld;
220 if (eth.getEtherType() == Ethernet.TYPE_BSN) {
221 ld = new DefaultLinkDescription(src, dst, Type.INDIRECT);
222 } else {
223 ld = new DefaultLinkDescription(src, dst, Type.DIRECT);
224 }
225 linkProvider.linkDetected(ld);
226 return true;
227 }
228 return false;
229 }
230
231
alshabib7911a052014-10-16 17:49:37 -0700232 /**
233 * Execute this method every t milliseconds. Loops over all ports
234 * labeled as fast and sends out an LLDP. Send out an LLDP on a single slow
235 * port.
236 *
237 * @param t timeout
alshabib7911a052014-10-16 17:49:37 -0700238 */
239 @Override
240 public void run(final Timeout t) {
Yuta HIGUCHIf6725882014-10-29 15:25:51 -0700241 boolean isMaster = mastershipService.getLocalRole(device.id()) == MASTER;
242 if (!isMaster) {
243 // reschedule timer
244 timeout = Timer.getTimer().newTimeout(this, this.probeRate, MILLISECONDS);
245 return;
246 }
247
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700248 this.log.trace("Sending probes from {}", device.id());
alshabib7911a052014-10-16 17:49:37 -0700249 synchronized (this) {
250 final Iterator<Long> fastIterator = this.fastPorts.iterator();
alshabib7911a052014-10-16 17:49:37 -0700251 while (fastIterator.hasNext()) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700252 long portNumber = fastIterator.next();
253 int probeCount = portProbeCount.get(portNumber).getAndIncrement();
alshabib7911a052014-10-16 17:49:37 -0700254
255 if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700256 this.log.trace("Sending fast probe to port {}", portNumber);
alshabib7911a052014-10-16 17:49:37 -0700257 sendProbes(portNumber);
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700258
alshabib7911a052014-10-16 17:49:37 -0700259 } else {
Yuta HIGUCHIf6725882014-10-29 15:25:51 -0700260 // Link down, demote to slowPorts
alshabib7911a052014-10-16 17:49:37 -0700261 // Update fast and slow ports
alshabibdfc7afb2014-10-21 20:13:27 -0700262 fastIterator.remove();
263 this.slowPorts.add(portNumber);
264 this.portProbeCount.remove(portNumber);
265
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700266 ConnectPoint cp = new ConnectPoint(device.id(),
267 portNumber(portNumber));
alshabib7911a052014-10-16 17:49:37 -0700268 log.debug("Link down -> {}", cp);
269 linkProvider.linksVanished(cp);
270 }
271 }
272
273 // send a probe for the next slow port
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700274 for (long portNumber : slowPorts) {
275 this.log.trace("Sending slow probe to port {}", portNumber);
276 sendProbes(portNumber);
alshabib7911a052014-10-16 17:49:37 -0700277 }
278 }
279
280 // reschedule timer
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700281 timeout = Timer.getTimer().newTimeout(this, this.probeRate, MILLISECONDS);
alshabib7911a052014-10-16 17:49:37 -0700282 }
283
284 public void stop() {
285 timeout.cancel();
alshabib0ed6a202014-10-19 12:42:57 -0700286 isStopped = true;
alshabib7911a052014-10-16 17:49:37 -0700287 }
288
289 public void start() {
Yuta HIGUCHIf6725882014-10-29 15:25:51 -0700290 if (isStopped) {
291 timeout = Timer.getTimer().newTimeout(this, 0, MILLISECONDS);
292 isStopped = false;
293 } else {
294 log.warn("LinkDiscovery started multiple times?");
295 }
alshabib7911a052014-10-16 17:49:37 -0700296 }
297
298 /**
299 * Creates packet_out LLDP for specified output port.
300 *
301 * @param port the port
302 * @return Packet_out message with LLDP data
303 */
304 private OutboundPacket createOutBoundLLDP(final Long port) {
305 if (port == null) {
306 return null;
307 }
308 this.lldpPacket.setPortId(port.intValue());
309 this.ethPacket.setSourceMACAddress("DE:AD:BE:EF:BA:11");
310
311 final byte[] lldp = this.ethPacket.serialize();
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700312 return new DefaultOutboundPacket(this.device.id(),
313 builder().setOutput(portNumber(port)).build(),
314 ByteBuffer.wrap(lldp));
alshabib7911a052014-10-16 17:49:37 -0700315 }
316
317 /**
318 * Creates packet_out BDDP for specified output port.
319 *
320 * @param port the port
321 * @return Packet_out message with LLDP data
322 */
323 private OutboundPacket createOutBoundBDDP(final Long port) {
324 if (port == null) {
325 return null;
326 }
327 this.lldpPacket.setPortId(port.intValue());
328 this.bddpEth.setSourceMACAddress("DE:AD:BE:EF:BA:11");
329
330 final byte[] bddp = this.bddpEth.serialize();
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700331 return new DefaultOutboundPacket(this.device.id(),
332 builder().setOutput(portNumber(port)).build(),
333 ByteBuffer.wrap(bddp));
alshabib7911a052014-10-16 17:49:37 -0700334 }
335
336 private void sendProbes(Long portNumber) {
Yuta HIGUCHIf6725882014-10-29 15:25:51 -0700337 // TODO: should have suppression port configuration, not by type
338 if (device.type() != Device.Type.ROADM) {
Jonathan Hart4f1ba092014-10-29 17:50:48 -0700339 log.trace("Sending probes out to {}@{}", portNumber, device.id());
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700340 OutboundPacket pkt = this.createOutBoundLLDP(portNumber);
341 pktService.emit(pkt);
342 if (useBDDP) {
343 OutboundPacket bpkt = this.createOutBoundBDDP(portNumber);
344 pktService.emit(bpkt);
345 }
346 }
alshabib7911a052014-10-16 17:49:37 -0700347 }
348
alshabib0ed6a202014-10-19 12:42:57 -0700349 public boolean containsPort(Long portNumber) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700350 return slowPorts.contains(portNumber) || fastPorts.contains(portNumber);
alshabib0ed6a202014-10-19 12:42:57 -0700351 }
352
353 public boolean isStopped() {
354 return isStopped;
355 }
356
alshabib7911a052014-10-16 17:49:37 -0700357}