blob: dd7242038e89249cf0983e8e03bef1e36217f831 [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
alshabib7911a052014-10-16 17:49:37 -070018import org.jboss.netty.util.Timeout;
19import org.jboss.netty.util.TimerTask;
alshabib875d6262014-10-17 16:19:40 -070020import org.onlab.onos.mastership.MastershipService;
alshabib7911a052014-10-16 17:49:37 -070021import org.onlab.onos.net.ConnectPoint;
22import org.onlab.onos.net.Device;
23import org.onlab.onos.net.DeviceId;
24import org.onlab.onos.net.Link.Type;
25import org.onlab.onos.net.Port;
26import org.onlab.onos.net.PortNumber;
alshabib7911a052014-10-16 17:49:37 -070027import org.onlab.onos.net.link.DefaultLinkDescription;
28import org.onlab.onos.net.link.LinkDescription;
29import org.onlab.onos.net.link.LinkProviderService;
30import org.onlab.onos.net.packet.DefaultOutboundPacket;
31import org.onlab.onos.net.packet.OutboundPacket;
32import org.onlab.onos.net.packet.PacketContext;
33import org.onlab.onos.net.packet.PacketService;
34import org.onlab.packet.Ethernet;
35import org.onlab.packet.ONOSLLDP;
36import org.onlab.util.Timer;
37import org.slf4j.Logger;
38
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -070039import java.nio.ByteBuffer;
40import java.util.Collections;
41import java.util.HashMap;
42import java.util.HashSet;
43import java.util.Iterator;
44import java.util.Map;
45import java.util.Set;
46import java.util.concurrent.atomic.AtomicInteger;
alshabib7911a052014-10-16 17:49:37 -070047
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -070048import static com.google.common.base.Preconditions.checkNotNull;
49import static java.util.concurrent.TimeUnit.MILLISECONDS;
50import static org.onlab.onos.net.MastershipRole.MASTER;
51import static org.onlab.onos.net.PortNumber.portNumber;
52import static org.onlab.onos.net.flow.DefaultTrafficTreatment.builder;
53import static org.slf4j.LoggerFactory.getLogger;
alshabib7911a052014-10-16 17:49:37 -070054
55/**
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;
71 private final Set<Long> fastPorts;
72 // number of unacknowledged probes per port
73 private final Map<Long, AtomicInteger> portProbeCount;
74 // number of probes to send before link is removed
75 private static final short MAX_PROBE_COUNT = 3;
76 private final Logger log = getLogger(getClass());
77 private final ONOSLLDP lldpPacket;
78 private final Ethernet ethPacket;
79 private Ethernet bddpEth;
80 private final boolean useBDDP;
81 private final LinkProviderService linkProvider;
82 private final PacketService pktService;
alshabib875d6262014-10-17 16:19:40 -070083 private final MastershipService mastershipService;
alshabib7911a052014-10-16 17:49:37 -070084 private Timeout timeout;
alshabib0ed6a202014-10-19 12:42:57 -070085 private boolean isStopped;
alshabib7911a052014-10-16 17:49:37 -070086
87 /**
88 * Instantiates discovery manager for the given physical switch. Creates a
89 * generic LLDP packet that will be customized for the port it is sent out on.
90 * Starts the the timer for the discovery process.
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -070091 *
92 * @param device the physical switch
93 * @param masterService mastership service
94 * @param useBDDP flag to also use BDDP for discovery
alshabib7911a052014-10-16 17:49:37 -070095 */
96 public LinkDiscovery(Device device, PacketService pktService,
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -070097 MastershipService masterService,
98 LinkProviderService providerService, Boolean... useBDDP) {
alshabib7911a052014-10-16 17:49:37 -070099 this.device = device;
100 this.probeRate = 3000;
101 this.linkProvider = providerService;
102 this.pktService = pktService;
alshabib3d643ec2014-10-22 18:33:00 -0700103
104 this.mastershipService = checkNotNull(masterService, "WTF!");
alshabib7911a052014-10-16 17:49:37 -0700105 this.slowPorts = Collections.synchronizedSet(new HashSet<Long>());
106 this.fastPorts = Collections.synchronizedSet(new HashSet<Long>());
107 this.portProbeCount = new HashMap<>();
108 this.lldpPacket = new ONOSLLDP();
109 this.lldpPacket.setChassisId(device.chassisId());
110 this.lldpPacket.setDevice(device.id().toString());
111
112
113 this.ethPacket = new Ethernet();
114 this.ethPacket.setEtherType(Ethernet.TYPE_LLDP);
115 this.ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA);
116 this.ethPacket.setPayload(this.lldpPacket);
117 this.ethPacket.setPad(true);
118 this.useBDDP = useBDDP.length > 0 ? useBDDP[0] : false;
119 if (this.useBDDP) {
120 this.bddpEth = new Ethernet();
121 this.bddpEth.setPayload(this.lldpPacket);
122 this.bddpEth.setEtherType(Ethernet.TYPE_BSN);
123 this.bddpEth.setDestinationMACAddress(ONOSLLDP.BDDP_MULTICAST);
124 this.bddpEth.setPad(true);
125 log.info("Using BDDP to discover network");
126 }
127
128 start();
129 this.log.debug("Started discovery manager for switch {}",
130 device.id());
131
132 }
133
134 /**
135 * Add physical port port to discovery process.
136 * Send out initial LLDP and label it as slow port.
137 *
138 * @param port the port
139 */
140 public void addPort(final Port port) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700141 this.log.debug("Sending init probe to port {}@{}",
142 port.number().toLong(), device.id());
alshabib7911a052014-10-16 17:49:37 -0700143 sendProbes(port.number().toLong());
alshabib7911a052014-10-16 17:49:37 -0700144 synchronized (this) {
145 this.slowPorts.add(port.number().toLong());
146 }
alshabib7911a052014-10-16 17:49:37 -0700147 }
148
149 /**
150 * Removes physical port from discovery process.
151 *
152 * @param port the port
153 */
154 public void removePort(final Port port) {
155 // Ignore ports that are not on this switch
156
157 long portnum = port.number().toLong();
158 synchronized (this) {
159 if (this.slowPorts.contains(portnum)) {
160 this.slowPorts.remove(portnum);
161
162 } else if (this.fastPorts.contains(portnum)) {
163 this.fastPorts.remove(portnum);
164 this.portProbeCount.remove(portnum);
165 // no iterator to update
166 } else {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700167 this.log.warn("Tried to dynamically remove non-existing port {}",
168 portnum);
alshabib7911a052014-10-16 17:49:37 -0700169 }
170 }
171 }
172
173 /**
174 * Method called by remote port to acknowledge receipt of LLDP sent by
175 * this port. If slow port, updates label to fast. If fast port, decrements
176 * number of unacknowledged probes.
177 *
178 * @param portNumber the port
179 */
180 public void ackProbe(final Long portNumber) {
alshabib7911a052014-10-16 17:49:37 -0700181 synchronized (this) {
182 if (this.slowPorts.contains(portNumber)) {
183 this.log.debug("Setting slow port to fast: {}:{}",
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700184 this.device.id(), portNumber);
alshabib7911a052014-10-16 17:49:37 -0700185 this.slowPorts.remove(portNumber);
186 this.fastPorts.add(portNumber);
187 this.portProbeCount.put(portNumber, new AtomicInteger(0));
alshabibacd91832014-10-17 14:38:41 -0700188 } else if (this.fastPorts.contains(portNumber)) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700189 this.portProbeCount.get(portNumber).set(0);
alshabibacd91832014-10-17 14:38:41 -0700190 } else {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700191 this.log.debug("Got ackProbe for non-existing port: {}", portNumber);
alshabib7911a052014-10-16 17:49:37 -0700192 }
193 }
194 }
195
196
197 /**
198 * Handles an incoming LLDP packet. Creates link in topology and sends ACK
199 * to port where LLDP originated.
200 */
201 public boolean handleLLDP(PacketContext context) {
202 Ethernet eth = context.inPacket().parsed();
203 ONOSLLDP onoslldp = ONOSLLDP.parseONOSLLDP(eth);
204 if (onoslldp != null) {
205 final PortNumber dstPort =
206 context.inPacket().receivedFrom().port();
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700207 final PortNumber srcPort = portNumber(onoslldp.getPort());
alshabib7911a052014-10-16 17:49:37 -0700208 final DeviceId srcDeviceId = DeviceId.deviceId(onoslldp.getDeviceString());
209 final DeviceId dstDeviceId = context.inPacket().receivedFrom().deviceId();
Jonathan Hart43ef46f2014-10-23 08:33:33 -0700210 this.ackProbe(dstPort.toLong());
alshabib7911a052014-10-16 17:49:37 -0700211 ConnectPoint src = new ConnectPoint(srcDeviceId, srcPort);
212 ConnectPoint dst = new ConnectPoint(dstDeviceId, dstPort);
213
214 LinkDescription ld;
215 if (eth.getEtherType() == Ethernet.TYPE_BSN) {
216 ld = new DefaultLinkDescription(src, dst, Type.INDIRECT);
217 } else {
218 ld = new DefaultLinkDescription(src, dst, Type.DIRECT);
219 }
220 linkProvider.linkDetected(ld);
221 return true;
222 }
223 return false;
224 }
225
226
alshabib7911a052014-10-16 17:49:37 -0700227 /**
228 * Execute this method every t milliseconds. Loops over all ports
229 * labeled as fast and sends out an LLDP. Send out an LLDP on a single slow
230 * port.
231 *
232 * @param t timeout
alshabib7911a052014-10-16 17:49:37 -0700233 */
234 @Override
235 public void run(final Timeout t) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700236 this.log.trace("Sending probes from {}", device.id());
alshabib7911a052014-10-16 17:49:37 -0700237 synchronized (this) {
238 final Iterator<Long> fastIterator = this.fastPorts.iterator();
alshabib7911a052014-10-16 17:49:37 -0700239 while (fastIterator.hasNext()) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700240 long portNumber = fastIterator.next();
241 int probeCount = portProbeCount.get(portNumber).getAndIncrement();
alshabib7911a052014-10-16 17:49:37 -0700242
243 if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700244 this.log.trace("Sending fast probe to port {}", portNumber);
alshabib7911a052014-10-16 17:49:37 -0700245 sendProbes(portNumber);
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700246
alshabib7911a052014-10-16 17:49:37 -0700247 } else {
248 // Update fast and slow ports
alshabibdfc7afb2014-10-21 20:13:27 -0700249 fastIterator.remove();
250 this.slowPorts.add(portNumber);
251 this.portProbeCount.remove(portNumber);
252
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700253 ConnectPoint cp = new ConnectPoint(device.id(),
254 portNumber(portNumber));
alshabib7911a052014-10-16 17:49:37 -0700255 log.debug("Link down -> {}", cp);
256 linkProvider.linksVanished(cp);
257 }
258 }
259
260 // send a probe for the next slow port
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700261 for (long portNumber : slowPorts) {
262 this.log.trace("Sending slow probe to port {}", portNumber);
263 sendProbes(portNumber);
alshabib7911a052014-10-16 17:49:37 -0700264 }
265 }
266
267 // reschedule timer
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700268 timeout = Timer.getTimer().newTimeout(this, this.probeRate, MILLISECONDS);
alshabib7911a052014-10-16 17:49:37 -0700269 }
270
271 public void stop() {
272 timeout.cancel();
alshabib0ed6a202014-10-19 12:42:57 -0700273 isStopped = true;
alshabib7911a052014-10-16 17:49:37 -0700274 }
275
276 public void start() {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700277 timeout = Timer.getTimer().newTimeout(this, 0, MILLISECONDS);
alshabib0ed6a202014-10-19 12:42:57 -0700278 isStopped = false;
alshabib7911a052014-10-16 17:49:37 -0700279 }
280
281 /**
282 * Creates packet_out LLDP for specified output port.
283 *
284 * @param port the port
285 * @return Packet_out message with LLDP data
286 */
287 private OutboundPacket createOutBoundLLDP(final Long port) {
288 if (port == null) {
289 return null;
290 }
291 this.lldpPacket.setPortId(port.intValue());
292 this.ethPacket.setSourceMACAddress("DE:AD:BE:EF:BA:11");
293
294 final byte[] lldp = this.ethPacket.serialize();
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700295 return new DefaultOutboundPacket(this.device.id(),
296 builder().setOutput(portNumber(port)).build(),
297 ByteBuffer.wrap(lldp));
alshabib7911a052014-10-16 17:49:37 -0700298 }
299
300 /**
301 * Creates packet_out BDDP for specified output port.
302 *
303 * @param port the port
304 * @return Packet_out message with LLDP data
305 */
306 private OutboundPacket createOutBoundBDDP(final Long port) {
307 if (port == null) {
308 return null;
309 }
310 this.lldpPacket.setPortId(port.intValue());
311 this.bddpEth.setSourceMACAddress("DE:AD:BE:EF:BA:11");
312
313 final byte[] bddp = this.bddpEth.serialize();
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700314 return new DefaultOutboundPacket(this.device.id(),
315 builder().setOutput(portNumber(port)).build(),
316 ByteBuffer.wrap(bddp));
alshabib7911a052014-10-16 17:49:37 -0700317 }
318
319 private void sendProbes(Long portNumber) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700320 boolean isMaster = mastershipService.getLocalRole(device.id()) == MASTER;
321 if (isMaster && device.type() != Device.Type.ROADM) {
322 log.debug("Sending probes out to {}@{}", portNumber, device.id());
323 OutboundPacket pkt = this.createOutBoundLLDP(portNumber);
324 pktService.emit(pkt);
325 if (useBDDP) {
326 OutboundPacket bpkt = this.createOutBoundBDDP(portNumber);
327 pktService.emit(bpkt);
328 }
329 }
alshabib7911a052014-10-16 17:49:37 -0700330 }
331
alshabib0ed6a202014-10-19 12:42:57 -0700332 public boolean containsPort(Long portNumber) {
Thomas Vachuskae1bcb0b2014-10-27 17:45:10 -0700333 return slowPorts.contains(portNumber) || fastPorts.contains(portNumber);
alshabib0ed6a202014-10-19 12:42:57 -0700334 }
335
336 public boolean isStopped() {
337 return isStopped;
338 }
339
alshabib7911a052014-10-16 17:49:37 -0700340}