blob: 1bb81f1f4d54d883b86f586231b329b93132d014 [file] [log] [blame]
Thomas Vachuska781d18b2014-10-27 10:31:25 -07001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
alshabib7911a052014-10-16 17:49:37 -07009 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
Thomas Vachuska781d18b2014-10-27 10:31:25 -070012 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
alshabib7911a052014-10-16 17:49:37 -070019package org.onlab.onos.provider.lldp.impl;
20
alshabib7911a052014-10-16 17:49:37 -070021import org.jboss.netty.util.Timeout;
22import org.jboss.netty.util.TimerTask;
alshabib875d6262014-10-17 16:19:40 -070023import org.onlab.onos.mastership.MastershipService;
alshabib7911a052014-10-16 17:49:37 -070024import org.onlab.onos.net.ConnectPoint;
25import org.onlab.onos.net.Device;
26import org.onlab.onos.net.DeviceId;
27import org.onlab.onos.net.Link.Type;
28import org.onlab.onos.net.Port;
29import org.onlab.onos.net.PortNumber;
alshabib7911a052014-10-16 17:49:37 -070030import org.onlab.onos.net.link.DefaultLinkDescription;
31import org.onlab.onos.net.link.LinkDescription;
32import org.onlab.onos.net.link.LinkProviderService;
33import org.onlab.onos.net.packet.DefaultOutboundPacket;
34import org.onlab.onos.net.packet.OutboundPacket;
35import org.onlab.onos.net.packet.PacketContext;
36import org.onlab.onos.net.packet.PacketService;
37import org.onlab.packet.Ethernet;
38import org.onlab.packet.ONOSLLDP;
39import org.onlab.util.Timer;
40import org.slf4j.Logger;
41
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -070042import java.nio.ByteBuffer;
43import java.util.Collections;
44import java.util.HashMap;
45import java.util.HashSet;
46import java.util.Iterator;
47import java.util.Map;
48import java.util.Set;
49import java.util.concurrent.atomic.AtomicInteger;
alshabib7911a052014-10-16 17:49:37 -070050
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -070051import static com.google.common.base.Preconditions.checkNotNull;
52import static java.util.concurrent.TimeUnit.MILLISECONDS;
53import static org.onlab.onos.net.MastershipRole.MASTER;
54import static org.onlab.onos.net.PortNumber.portNumber;
55import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
56import static org.slf4j.LoggerFactory.getLogger;
alshabib7911a052014-10-16 17:49:37 -070057
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.
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -070064 * <p/>
alshabib7911a052014-10-16 17:49:37 -070065 * 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 Device device;
71 // send 1 probe every probeRate milliseconds
72 private final long probeRate;
73 private final Set<Long> slowPorts;
74 private final Set<Long> fastPorts;
75 // number of unacknowledged probes per port
76 private final Map<Long, AtomicInteger> portProbeCount;
77 // number of probes to send before link is removed
78 private static final short MAX_PROBE_COUNT = 3;
79 private final Logger log = getLogger(getClass());
80 private final ONOSLLDP lldpPacket;
81 private final Ethernet ethPacket;
82 private Ethernet bddpEth;
83 private final boolean useBDDP;
84 private final LinkProviderService linkProvider;
85 private final PacketService pktService;
alshabib875d6262014-10-17 16:19:40 -070086 private final MastershipService mastershipService;
alshabib7911a052014-10-16 17:49:37 -070087 private Timeout timeout;
alshabib0ed6a202014-10-19 12:42:57 -070088 private boolean isStopped;
alshabib7911a052014-10-16 17:49:37 -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.
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -070094 *
95 * @param device the physical switch
96 * @param masterService mastership service
97 * @param useBDDP flag to also use BDDP for discovery
alshabib7911a052014-10-16 17:49:37 -070098 */
99 public LinkDiscovery(Device device, PacketService pktService,
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700100 MastershipService masterService,
101 LinkProviderService providerService, Boolean... useBDDP) {
alshabib7911a052014-10-16 17:49:37 -0700102 this.device = device;
103 this.probeRate = 3000;
104 this.linkProvider = providerService;
105 this.pktService = pktService;
alshabib3d643ec2014-10-22 18:33:00 -0700106
107 this.mastershipService = checkNotNull(masterService, "WTF!");
alshabib7911a052014-10-16 17:49:37 -0700108 this.slowPorts = Collections.synchronizedSet(new HashSet<Long>());
109 this.fastPorts = Collections.synchronizedSet(new HashSet<Long>());
110 this.portProbeCount = new HashMap<>();
111 this.lldpPacket = new ONOSLLDP();
112 this.lldpPacket.setChassisId(device.chassisId());
113 this.lldpPacket.setDevice(device.id().toString());
114
115
116 this.ethPacket = new Ethernet();
117 this.ethPacket.setEtherType(Ethernet.TYPE_LLDP);
118 this.ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA);
119 this.ethPacket.setPayload(this.lldpPacket);
120 this.ethPacket.setPad(true);
121 this.useBDDP = useBDDP.length > 0 ? useBDDP[0] : false;
122 if (this.useBDDP) {
123 this.bddpEth = new Ethernet();
124 this.bddpEth.setPayload(this.lldpPacket);
125 this.bddpEth.setEtherType(Ethernet.TYPE_BSN);
126 this.bddpEth.setDestinationMACAddress(ONOSLLDP.BDDP_MULTICAST);
127 this.bddpEth.setPad(true);
128 log.info("Using BDDP to discover network");
129 }
130
131 start();
132 this.log.debug("Started discovery manager for switch {}",
133 device.id());
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 Port port) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700144 this.log.debug("Sending init probe to port {}@{}",
145 port.number().toLong(), device.id());
alshabib7911a052014-10-16 17:49:37 -0700146 sendProbes(port.number().toLong());
alshabib7911a052014-10-16 17:49:37 -0700147 synchronized (this) {
148 this.slowPorts.add(port.number().toLong());
149 }
alshabib7911a052014-10-16 17:49:37 -0700150 }
151
152 /**
153 * Removes physical port from discovery process.
154 *
155 * @param port the port
156 */
157 public void removePort(final Port port) {
158 // Ignore ports that are not on this switch
159
160 long portnum = port.number().toLong();
161 synchronized (this) {
162 if (this.slowPorts.contains(portnum)) {
163 this.slowPorts.remove(portnum);
164
165 } else if (this.fastPorts.contains(portnum)) {
166 this.fastPorts.remove(portnum);
167 this.portProbeCount.remove(portnum);
168 // no iterator to update
169 } else {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700170 this.log.warn("Tried to dynamically remove non-existing port {}",
171 portnum);
alshabib7911a052014-10-16 17:49:37 -0700172 }
173 }
174 }
175
176 /**
177 * Method called by remote port to acknowledge receipt of LLDP sent by
178 * this port. If slow port, updates label to fast. If fast port, decrements
179 * number of unacknowledged probes.
180 *
181 * @param portNumber the port
182 */
183 public void ackProbe(final Long portNumber) {
alshabib7911a052014-10-16 17:49:37 -0700184 synchronized (this) {
185 if (this.slowPorts.contains(portNumber)) {
186 this.log.debug("Setting slow port to fast: {}:{}",
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700187 this.device.id(), portNumber);
alshabib7911a052014-10-16 17:49:37 -0700188 this.slowPorts.remove(portNumber);
189 this.fastPorts.add(portNumber);
190 this.portProbeCount.put(portNumber, new AtomicInteger(0));
alshabibacd91832014-10-17 14:38:41 -0700191 } else if (this.fastPorts.contains(portNumber)) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700192 this.portProbeCount.get(portNumber).set(0);
alshabibacd91832014-10-17 14:38:41 -0700193 } else {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700194 this.log.debug("Got ackProbe for non-existing port: {}", portNumber);
alshabib7911a052014-10-16 17:49:37 -0700195 }
196 }
197 }
198
199
200 /**
201 * Handles an incoming LLDP packet. Creates link in topology and sends ACK
202 * to port where LLDP originated.
203 */
204 public boolean handleLLDP(PacketContext context) {
205 Ethernet eth = context.inPacket().parsed();
206 ONOSLLDP onoslldp = ONOSLLDP.parseONOSLLDP(eth);
207 if (onoslldp != null) {
208 final PortNumber dstPort =
209 context.inPacket().receivedFrom().port();
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700210 final PortNumber srcPort = portNumber(onoslldp.getPort());
alshabib7911a052014-10-16 17:49:37 -0700211 final DeviceId srcDeviceId = DeviceId.deviceId(onoslldp.getDeviceString());
212 final DeviceId dstDeviceId = context.inPacket().receivedFrom().deviceId();
Jonathan Hart43ef46f2014-10-23 08:33:33 -0700213 this.ackProbe(dstPort.toLong());
alshabib7911a052014-10-16 17:49:37 -0700214 ConnectPoint src = new ConnectPoint(srcDeviceId, srcPort);
215 ConnectPoint dst = new ConnectPoint(dstDeviceId, dstPort);
216
217 LinkDescription ld;
218 if (eth.getEtherType() == Ethernet.TYPE_BSN) {
219 ld = new DefaultLinkDescription(src, dst, Type.INDIRECT);
220 } else {
221 ld = new DefaultLinkDescription(src, dst, Type.DIRECT);
222 }
223 linkProvider.linkDetected(ld);
224 return true;
225 }
226 return false;
227 }
228
229
alshabib7911a052014-10-16 17:49:37 -0700230 /**
231 * Execute this method every t milliseconds. Loops over all ports
232 * labeled as fast and sends out an LLDP. Send out an LLDP on a single slow
233 * port.
234 *
235 * @param t timeout
alshabib7911a052014-10-16 17:49:37 -0700236 */
237 @Override
238 public void run(final Timeout t) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700239 this.log.trace("Sending probes from {}", device.id());
alshabib7911a052014-10-16 17:49:37 -0700240 synchronized (this) {
241 final Iterator<Long> fastIterator = this.fastPorts.iterator();
alshabib7911a052014-10-16 17:49:37 -0700242 while (fastIterator.hasNext()) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700243 long portNumber = fastIterator.next();
244 int probeCount = portProbeCount.get(portNumber).getAndIncrement();
alshabib7911a052014-10-16 17:49:37 -0700245
246 if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700247 this.log.trace("Sending fast probe to port {}", portNumber);
alshabib7911a052014-10-16 17:49:37 -0700248 sendProbes(portNumber);
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700249
alshabib7911a052014-10-16 17:49:37 -0700250 } else {
251 // Update fast and slow ports
alshabibdfc7afb2014-10-21 20:13:27 -0700252 fastIterator.remove();
253 this.slowPorts.add(portNumber);
254 this.portProbeCount.remove(portNumber);
255
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700256 ConnectPoint cp = new ConnectPoint(device.id(),
257 portNumber(portNumber));
alshabib7911a052014-10-16 17:49:37 -0700258 log.debug("Link down -> {}", cp);
259 linkProvider.linksVanished(cp);
260 }
261 }
262
263 // send a probe for the next slow port
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700264 for (long portNumber : slowPorts) {
265 this.log.trace("Sending slow probe to port {}", portNumber);
266 sendProbes(portNumber);
alshabib7911a052014-10-16 17:49:37 -0700267 }
268 }
269
270 // reschedule timer
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700271 timeout = Timer.getTimer().newTimeout(this, this.probeRate, MILLISECONDS);
alshabib7911a052014-10-16 17:49:37 -0700272 }
273
274 public void stop() {
275 timeout.cancel();
alshabib0ed6a202014-10-19 12:42:57 -0700276 isStopped = true;
alshabib7911a052014-10-16 17:49:37 -0700277 }
278
279 public void start() {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700280 timeout = Timer.getTimer().newTimeout(this, 0, MILLISECONDS);
alshabib0ed6a202014-10-19 12:42:57 -0700281 isStopped = false;
alshabib7911a052014-10-16 17:49:37 -0700282 }
283
284 /**
285 * Creates packet_out LLDP for specified output port.
286 *
287 * @param port the port
288 * @return Packet_out message with LLDP data
289 */
290 private OutboundPacket createOutBoundLLDP(final Long port) {
291 if (port == null) {
292 return null;
293 }
294 this.lldpPacket.setPortId(port.intValue());
295 this.ethPacket.setSourceMACAddress("DE:AD:BE:EF:BA:11");
296
297 final byte[] lldp = this.ethPacket.serialize();
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700298 return new DefaultOutboundPacket(this.device.id(),
299 builder().setOutput(portNumber(port)).build(),
300 ByteBuffer.wrap(lldp));
alshabib7911a052014-10-16 17:49:37 -0700301 }
302
303 /**
304 * Creates packet_out BDDP for specified output port.
305 *
306 * @param port the port
307 * @return Packet_out message with LLDP data
308 */
309 private OutboundPacket createOutBoundBDDP(final Long port) {
310 if (port == null) {
311 return null;
312 }
313 this.lldpPacket.setPortId(port.intValue());
314 this.bddpEth.setSourceMACAddress("DE:AD:BE:EF:BA:11");
315
316 final byte[] bddp = this.bddpEth.serialize();
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700317 return new DefaultOutboundPacket(this.device.id(),
318 builder().setOutput(portNumber(port)).build(),
319 ByteBuffer.wrap(bddp));
alshabib7911a052014-10-16 17:49:37 -0700320 }
321
322 private void sendProbes(Long portNumber) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700323 boolean isMaster = mastershipService.getLocalRole(device.id()) == MASTER;
324 if (isMaster && device.type() != Device.Type.ROADM) {
325 log.debug("Sending probes out to {}@{}", portNumber, device.id());
326 OutboundPacket pkt = this.createOutBoundLLDP(portNumber);
327 pktService.emit(pkt);
328 if (useBDDP) {
329 OutboundPacket bpkt = this.createOutBoundBDDP(portNumber);
330 pktService.emit(bpkt);
331 }
332 }
alshabib7911a052014-10-16 17:49:37 -0700333 }
334
alshabib0ed6a202014-10-19 12:42:57 -0700335 public boolean containsPort(Long portNumber) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700336 return slowPorts.contains(portNumber) || fastPorts.contains(portNumber);
alshabib0ed6a202014-10-19 12:42:57 -0700337 }
338
339 public boolean isStopped() {
340 return isStopped;
341 }
342
alshabib7911a052014-10-16 17:49:37 -0700343}