a81eeb1d24b34780536fe0a0fb3c7ed5f58978cf
[onosfw.git] /
1 /*
2  * Copyright 2014-2015 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  */
16 package org.onosproject.provider.lldp.impl;
17
18 import com.google.common.collect.Maps;
19 import com.google.common.collect.Sets;
20 import org.jboss.netty.util.Timeout;
21 import org.jboss.netty.util.TimerTask;
22 import org.onlab.packet.Ethernet;
23 import org.onlab.packet.ONOSLLDP;
24 import org.onlab.util.Timer;
25 import org.onosproject.mastership.MastershipService;
26 import org.onosproject.net.ConnectPoint;
27 import org.onosproject.net.Device;
28 import org.onosproject.net.DeviceId;
29 import org.onosproject.net.Link.Type;
30 import org.onosproject.net.Port;
31 import org.onosproject.net.PortNumber;
32 import org.onosproject.net.link.DefaultLinkDescription;
33 import org.onosproject.net.link.LinkDescription;
34 import org.onosproject.net.link.LinkProviderService;
35 import org.onosproject.net.packet.DefaultOutboundPacket;
36 import org.onosproject.net.packet.OutboundPacket;
37 import org.onosproject.net.packet.PacketContext;
38 import org.onosproject.net.packet.PacketService;
39 import org.slf4j.Logger;
40
41 import java.nio.ByteBuffer;
42 import java.util.Iterator;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.concurrent.atomic.AtomicInteger;
46
47 import static com.google.common.base.Preconditions.checkNotNull;
48 import static java.util.concurrent.TimeUnit.MILLISECONDS;
49 import static org.onosproject.net.PortNumber.portNumber;
50 import static org.onosproject.net.flow.DefaultTrafficTreatment.builder;
51 import static org.slf4j.LoggerFactory.getLogger;
52
53 // TODO: add 'fast discovery' mode: drop LLDPs in destination switch but listen for flow_removed messages
54 // FIXME: add ability to track links using port pairs or the link inventory
55
56 /**
57  * Run discovery process from a physical switch. Ports are initially labeled as
58  * slow ports. When an LLDP is successfully received, label the remote port as
59  * fast. Every probeRate milliseconds, loop over all fast ports and send an
60  * LLDP, send an LLDP for a single slow port. Based on FlowVisor topology
61  * discovery implementation.
62  */
63 public class LinkDiscovery implements TimerTask {
64
65     private final Logger log = getLogger(getClass());
66
67     private static final short MAX_PROBE_COUNT = 3; // probes to send before link is removed
68     private static final short DEFAULT_PROBE_RATE = 3000; // millis
69     private static final String SRC_MAC = "DE:AD:BE:EF:BA:11";
70     private static final String SERVICE_NULL = "Service cannot be null";
71
72     private final Device device;
73
74     // send 1 probe every probeRate milliseconds
75     private final long probeRate = DEFAULT_PROBE_RATE;
76
77     private final Set<Long> slowPorts = Sets.newConcurrentHashSet();
78     // ports, known to have incoming links
79     private final Set<Long> fastPorts = Sets.newConcurrentHashSet();
80
81     // number of unacknowledged probes per port
82     private final Map<Long, AtomicInteger> portProbeCount = Maps.newHashMap();
83
84     private final ONOSLLDP lldpPacket;
85     private final Ethernet ethPacket;
86     private Ethernet bddpEth;
87     private final boolean useBDDP;
88
89     private Timeout timeout;
90     private volatile boolean isStopped;
91
92     private final LinkProviderService linkProvider;
93     private final PacketService pktService;
94     private final MastershipService mastershipService;
95
96     /**
97      * Instantiates discovery manager for the given physical switch. Creates a
98      * generic LLDP packet that will be customized for the port it is sent out on.
99      * Starts the the timer for the discovery process.
100      *
101      * @param device          the physical switch
102      * @param pktService      packet service
103      * @param masterService   mastership service
104      * @param providerService link provider service
105      * @param useBDDP         flag to also use BDDP for discovery
106      */
107     public LinkDiscovery(Device device, PacketService pktService,
108                          MastershipService masterService,
109                          LinkProviderService providerService, Boolean... useBDDP) {
110         this.device = device;
111         this.linkProvider = checkNotNull(providerService, SERVICE_NULL);
112         this.pktService = checkNotNull(pktService, SERVICE_NULL);
113         this.mastershipService = checkNotNull(masterService, SERVICE_NULL);
114
115         lldpPacket = new ONOSLLDP();
116         lldpPacket.setChassisId(device.chassisId());
117         lldpPacket.setDevice(device.id().toString());
118
119         ethPacket = new Ethernet();
120         ethPacket.setEtherType(Ethernet.TYPE_LLDP);
121         ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA);
122         ethPacket.setPayload(this.lldpPacket);
123         ethPacket.setPad(true);
124
125         this.useBDDP = useBDDP.length > 0 ? useBDDP[0] : false;
126         if (this.useBDDP) {
127             bddpEth = new Ethernet();
128             bddpEth.setPayload(lldpPacket);
129             bddpEth.setEtherType(Ethernet.TYPE_BSN);
130             bddpEth.setDestinationMACAddress(ONOSLLDP.BDDP_MULTICAST);
131             bddpEth.setPad(true);
132             log.info("Using BDDP to discover network");
133         }
134
135         isStopped = true;
136         start();
137         log.debug("Started discovery manager for switch {}", device.id());
138
139     }
140
141     /**
142      * Add physical port port to discovery process.
143      * Send out initial LLDP and label it as slow port.
144      *
145      * @param port the port
146      */
147     public void addPort(Port port) {
148         boolean newPort = false;
149         synchronized (this) {
150             if (!containsPort(port.number().toLong())) {
151                 newPort = true;
152                 slowPorts.add(port.number().toLong());
153             }
154         }
155
156         boolean isMaster = mastershipService.isLocalMaster(device.id());
157         if (newPort && isMaster) {
158             log.debug("Sending init probe to port {}@{}", port.number().toLong(), device.id());
159             sendProbes(port.number().toLong());
160         }
161     }
162
163     /**
164      * Removes physical port from discovery process.
165      *
166      * @param port the port
167      */
168     public void removePort(Port port) {
169         // Ignore ports that are not on this switch
170         long portnum = port.number().toLong();
171         synchronized (this) {
172             if (slowPorts.contains(portnum)) {
173                 slowPorts.remove(portnum);
174
175             } else if (fastPorts.contains(portnum)) {
176                 fastPorts.remove(portnum);
177                 portProbeCount.remove(portnum);
178                 // no iterator to update
179             } else {
180                 log.warn("Tried to dynamically remove non-existing port {}", portnum);
181             }
182         }
183     }
184
185     /**
186      * Method called by remote port to acknowledge receipt of LLDP sent by
187      * this port. If slow port, updates label to fast. If fast port, decrements
188      * number of unacknowledged probes.
189      *
190      * @param portNumber the port
191      */
192     public void ackProbe(Long portNumber) {
193         synchronized (this) {
194             if (slowPorts.contains(portNumber)) {
195                 log.debug("Setting slow port to fast: {}:{}", device.id(), portNumber);
196                 slowPorts.remove(portNumber);
197                 fastPorts.add(portNumber);
198                 portProbeCount.put(portNumber, new AtomicInteger(0));
199             } else if (fastPorts.contains(portNumber)) {
200                 portProbeCount.get(portNumber).set(0);
201             } else {
202                 log.debug("Got ackProbe for non-existing port: {}", portNumber);
203             }
204         }
205     }
206
207
208     /**
209      * Handles an incoming LLDP packet. Creates link in topology and sends ACK
210      * to port where LLDP originated.
211      *
212      * @param context packet context
213      * @return true if handled
214      */
215     public boolean handleLLDP(PacketContext context) {
216         Ethernet eth = context.inPacket().parsed();
217         if (eth == null) {
218             return false;
219         }
220
221         ONOSLLDP onoslldp = ONOSLLDP.parseONOSLLDP(eth);
222         if (onoslldp != null) {
223             PortNumber srcPort = portNumber(onoslldp.getPort());
224             PortNumber dstPort = context.inPacket().receivedFrom().port();
225             DeviceId srcDeviceId = DeviceId.deviceId(onoslldp.getDeviceString());
226             DeviceId dstDeviceId = context.inPacket().receivedFrom().deviceId();
227             ackProbe(dstPort.toLong());
228
229             ConnectPoint src = new ConnectPoint(srcDeviceId, srcPort);
230             ConnectPoint dst = new ConnectPoint(dstDeviceId, dstPort);
231
232             LinkDescription ld = eth.getEtherType() == Ethernet.TYPE_LLDP ?
233                     new DefaultLinkDescription(src, dst, Type.DIRECT) :
234                     new DefaultLinkDescription(src, dst, Type.INDIRECT);
235
236             try {
237                 linkProvider.linkDetected(ld);
238             } catch (IllegalStateException e) {
239                 return true;
240             }
241             return true;
242         }
243         return false;
244     }
245
246
247     /**
248      * Execute this method every t milliseconds. Loops over all ports
249      * labeled as fast and sends out an LLDP. Send out an LLDP on a single slow
250      * port.
251      *
252      * @param t timeout
253      */
254     @Override
255     public void run(Timeout t) {
256         if (isStopped()) {
257             return;
258         }
259         if (!mastershipService.isLocalMaster(device.id())) {
260             if (!isStopped()) {
261                 // reschedule timer
262                 timeout = Timer.getTimer().newTimeout(this, probeRate, MILLISECONDS);
263             }
264             return;
265         }
266
267         log.trace("Sending probes from {}", device.id());
268         synchronized (this) {
269             Iterator<Long> fastIterator = fastPorts.iterator();
270             while (fastIterator.hasNext()) {
271                 long portNumber = fastIterator.next();
272                 int probeCount = portProbeCount.get(portNumber).getAndIncrement();
273
274                 if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) {
275                     log.trace("Sending fast probe to port {}", portNumber);
276                     sendProbes(portNumber);
277
278                 } else {
279                     // Link down, demote to slowPorts; update fast and slow ports
280                     fastIterator.remove();
281                     slowPorts.add(portNumber);
282                     portProbeCount.remove(portNumber);
283
284                     ConnectPoint cp = new ConnectPoint(device.id(), portNumber(portNumber));
285                     log.debug("Link down -> {}", cp);
286                     linkProvider.linksVanished(cp);
287                 }
288             }
289
290             // send a probe for the next slow port
291             for (long portNumber : slowPorts) {
292                 log.trace("Sending slow probe to port {}", portNumber);
293                 sendProbes(portNumber);
294             }
295         }
296
297         if (!isStopped()) {
298             // reschedule timer
299             timeout = Timer.getTimer().newTimeout(this, probeRate, MILLISECONDS);
300         }
301     }
302
303     public synchronized void stop() {
304         isStopped = true;
305         timeout.cancel();
306     }
307
308     public synchronized void start() {
309         if (isStopped) {
310             isStopped = false;
311             timeout = Timer.getTimer().newTimeout(this, 0, MILLISECONDS);
312         } else {
313             log.warn("LinkDiscovery started multiple times?");
314         }
315     }
316
317     /**
318      * Creates packet_out LLDP for specified output port.
319      *
320      * @param port the port
321      * @return Packet_out message with LLDP data
322      */
323     private OutboundPacket createOutBoundLLDP(Long port) {
324         if (port == null) {
325             return null;
326         }
327         lldpPacket.setPortId(port.intValue());
328         ethPacket.setSourceMACAddress(SRC_MAC);
329         return new DefaultOutboundPacket(device.id(),
330                                          builder().setOutput(portNumber(port)).build(),
331                                          ByteBuffer.wrap(ethPacket.serialize()));
332     }
333
334     /**
335      * Creates packet_out BDDP for specified output port.
336      *
337      * @param port the port
338      * @return Packet_out message with LLDP data
339      */
340     private OutboundPacket createOutBoundBDDP(Long port) {
341         if (port == null) {
342             return null;
343         }
344         lldpPacket.setPortId(port.intValue());
345         bddpEth.setSourceMACAddress(SRC_MAC);
346         return new DefaultOutboundPacket(device.id(),
347                                          builder().setOutput(portNumber(port)).build(),
348                                          ByteBuffer.wrap(bddpEth.serialize()));
349     }
350
351     private void sendProbes(Long portNumber) {
352         log.trace("Sending probes out to {}@{}", portNumber, device.id());
353         OutboundPacket pkt = createOutBoundLLDP(portNumber);
354         pktService.emit(pkt);
355         if (useBDDP) {
356             OutboundPacket bpkt = createOutBoundBDDP(portNumber);
357             pktService.emit(bpkt);
358         }
359     }
360
361     public boolean containsPort(Long portNumber) {
362         return slowPorts.contains(portNumber) || fastPorts.contains(portNumber);
363     }
364
365     public synchronized boolean isStopped() {
366         return isStopped || timeout.isCancelled();
367     }
368
369 }