| /******************************************************************************* |
| * Copyright 2014 Open Networking Laboratory |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| ******************************************************************************/ |
| package org.onlab.onos.provider.of.link.impl; |
| |
| import static org.onlab.onos.of.controller.Dpid.uri; |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import org.jboss.netty.util.Timeout; |
| import org.jboss.netty.util.TimerTask; |
| import org.onlab.onos.net.ConnectPoint; |
| import org.onlab.onos.net.DeviceId; |
| import org.onlab.onos.net.Link.Type; |
| import org.onlab.onos.net.PortNumber; |
| import org.onlab.onos.net.link.DefaultLinkDescription; |
| import org.onlab.onos.net.link.LinkDescription; |
| import org.onlab.onos.net.link.LinkProviderService; |
| import org.onlab.onos.of.controller.Dpid; |
| import org.onlab.onos.of.controller.OpenFlowController; |
| import org.onlab.onos.of.controller.OpenFlowSwitch; |
| import org.onlab.packet.Ethernet; |
| import org.onlab.packet.ONLabLddp; |
| import org.onlab.packet.ONLabLddp.DPIDandPort; |
| import org.onlab.util.Timer; |
| import org.projectfloodlight.openflow.protocol.OFFactory; |
| import org.projectfloodlight.openflow.protocol.OFMessage; |
| import org.projectfloodlight.openflow.protocol.OFPacketOut; |
| import org.projectfloodlight.openflow.protocol.OFPortDesc; |
| import org.projectfloodlight.openflow.protocol.action.OFAction; |
| import org.projectfloodlight.openflow.protocol.action.OFActionOutput; |
| import org.projectfloodlight.openflow.types.OFBufferId; |
| import org.projectfloodlight.openflow.types.OFPort; |
| import org.slf4j.Logger; |
| |
| |
| |
| /** |
| * Run discovery process from a physical switch. Ports are initially labeled as |
| * slow ports. When an LLDP is successfully received, label the remote port as |
| * fast. Every probeRate milliseconds, loop over all fast ports and send an |
| * LLDP, send an LLDP for a single slow port. Based on FlowVisor topology |
| * discovery implementation. |
| * |
| * TODO: add 'fast discovery' mode: drop LLDPs in destination switch but listen |
| * for flow_removed messages |
| */ |
| public class LinkDiscovery implements TimerTask { |
| |
| private final OpenFlowSwitch sw; |
| // send 1 probe every probeRate milliseconds |
| private final long probeRate; |
| private final Set<Integer> slowPorts; |
| private final Set<Integer> fastPorts; |
| // number of unacknowledged probes per port |
| private final Map<Integer, AtomicInteger> portProbeCount; |
| // number of probes to send before link is removed |
| private static final short MAX_PROBE_COUNT = 3; |
| private Iterator<Integer> slowIterator; |
| private final OFFactory ofFactory; |
| private final Logger log = getLogger(getClass()); |
| private final ONLabLddp lldpPacket; |
| private final Ethernet ethPacket; |
| private Ethernet bddpEth; |
| private final boolean useBDDP; |
| private final OpenFlowController ctrl; |
| private final LinkProviderService linkProvider; |
| private final Map<Integer, OFPortDesc> ports; |
| private Timeout timeout; |
| |
| /** |
| * Instantiates discovery manager for the given physical switch. Creates a |
| * generic LLDP packet that will be customized for the port it is sent out on. |
| * Starts the the timer for the discovery process. |
| * |
| * @param sw the physical switch |
| * @param useBDDP flag to also use BDDP for discovery |
| */ |
| public LinkDiscovery(final OpenFlowSwitch sw, |
| OpenFlowController ctrl, LinkProviderService providerService, Boolean... useBDDP) { |
| this.sw = sw; |
| this.ofFactory = sw.factory(); |
| this.ctrl = ctrl; |
| this.probeRate = 1000; |
| this.linkProvider = providerService; |
| this.slowPorts = Collections.synchronizedSet(new HashSet<Integer>()); |
| this.fastPorts = Collections.synchronizedSet(new HashSet<Integer>()); |
| this.ports = new ConcurrentHashMap<>(); |
| this.portProbeCount = new HashMap<Integer, AtomicInteger>(); |
| this.lldpPacket = new ONLabLddp(); |
| this.lldpPacket.setSwitch(this.sw.getId()); |
| this.ethPacket = new Ethernet(); |
| this.ethPacket.setEtherType(Ethernet.TYPE_LLDP); |
| this.ethPacket.setDestinationMACAddress(ONLabLddp.LLDP_NICIRA); |
| this.ethPacket.setPayload(this.lldpPacket); |
| this.ethPacket.setPad(true); |
| this.useBDDP = useBDDP.length > 0 ? useBDDP[0] : false; |
| if (this.useBDDP) { |
| this.bddpEth = new Ethernet(); |
| this.bddpEth.setPayload(this.lldpPacket); |
| this.bddpEth.setEtherType(Ethernet.TYPE_BSN); |
| this.bddpEth.setDestinationMACAddress(ONLabLddp.BDDP_MULTICAST); |
| this.bddpEth.setPad(true); |
| log.info("Using BDDP to discover network"); |
| } |
| for (OFPortDesc port : sw.getPorts()) { |
| if (port.getPortNo() != OFPort.LOCAL) { |
| addPort(port); |
| } |
| } |
| timeout = Timer.getTimer().newTimeout(this, this.probeRate, |
| TimeUnit.MILLISECONDS); |
| this.log.debug("Started discovery manager for switch {}", |
| sw.getId()); |
| |
| } |
| |
| /** |
| * Add physical port port to discovery process. |
| * Send out initial LLDP and label it as slow port. |
| * |
| * @param port the port |
| */ |
| public void addPort(final OFPortDesc port) { |
| // Ignore ports that are not on this switch, or already booted. */ |
| this.ports.put(port.getPortNo().getPortNumber(), port); |
| synchronized (this) { |
| this.log.debug("sending init probe to port {}", |
| port.getPortNo().getPortNumber()); |
| OFPacketOut pkt; |
| |
| pkt = this.createLLDPPacketOut(port); |
| this.sw.sendMsg(pkt); |
| if (useBDDP) { |
| OFPacketOut bpkt = this.createBDDPPacketOut(port); |
| this.sw.sendMsg(bpkt); |
| } |
| |
| this.slowPorts.add(port.getPortNo().getPortNumber()); |
| this.slowIterator = this.slowPorts.iterator(); |
| } |
| |
| } |
| |
| /** |
| * Removes physical port from discovery process. |
| * |
| * @param port the port |
| */ |
| public void removePort(final OFPortDesc port) { |
| // Ignore ports that are not on this switch |
| |
| int portnum = port.getPortNo().getPortNumber(); |
| this.ports.remove(portnum); |
| synchronized (this) { |
| if (this.slowPorts.contains(portnum)) { |
| this.slowPorts.remove(portnum); |
| this.slowIterator = this.slowPorts.iterator(); |
| |
| } else if (this.fastPorts.contains(portnum)) { |
| this.fastPorts.remove(portnum); |
| this.portProbeCount.remove(portnum); |
| // no iterator to update |
| } else { |
| this.log.warn( |
| "tried to dynamically remove non-existing port {}", |
| portnum); |
| } |
| } |
| ConnectPoint cp = new ConnectPoint( |
| DeviceId.deviceId(uri(sw.getId())), |
| PortNumber.portNumber(port.getPortNo().getPortNumber())); |
| linkProvider.linksVanished(cp); |
| |
| } |
| |
| /** |
| * Method called by remote port to acknowledge receipt of LLDP sent by |
| * this port. If slow port, updates label to fast. If fast port, decrements |
| * number of unacknowledged probes. |
| * |
| * @param port the port |
| */ |
| public void ackProbe(final Integer port) { |
| final int portNumber = port; |
| synchronized (this) { |
| if (this.slowPorts.contains(portNumber)) { |
| this.log.debug("Setting slow port to fast: {}:{}", |
| this.sw.getId(), portNumber); |
| this.slowPorts.remove(portNumber); |
| this.slowIterator = this.slowPorts.iterator(); |
| this.fastPorts.add(portNumber); |
| this.portProbeCount.put(portNumber, new AtomicInteger(0)); |
| } else { |
| if (this.fastPorts.contains(portNumber)) { |
| this.portProbeCount.get(portNumber).set(0); |
| } else { |
| this.log.debug( |
| "Got ackProbe for non-existing port: {}", |
| portNumber); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Creates packet_out LLDP for specified output port. |
| * |
| * @param port the port |
| * @return Packet_out message with LLDP data |
| */ |
| private OFPacketOut createLLDPPacketOut(final OFPortDesc port) { |
| OFPacketOut.Builder packetOut = this.ofFactory.buildPacketOut(); |
| packetOut.setBufferId(OFBufferId.NO_BUFFER); |
| OFAction act = this.ofFactory.actions().buildOutput() |
| .setPort(port.getPortNo()).build(); |
| packetOut.setActions(Collections.singletonList(act)); |
| this.lldpPacket.setPort(port.getPortNo().getPortNumber()); |
| this.ethPacket.setSourceMACAddress(port.getHwAddr().getBytes()); |
| |
| final byte[] lldp = this.ethPacket.serialize(); |
| packetOut.setData(lldp); |
| return packetOut.build(); |
| } |
| |
| /** |
| * Creates packet_out BDDP for specified output port. |
| * |
| * @param port the port |
| * @return Packet_out message with LLDP data |
| */ |
| private OFPacketOut createBDDPPacketOut(final OFPortDesc port) { |
| OFPacketOut.Builder packetOut = sw.factory().buildPacketOut(); |
| |
| packetOut.setBufferId(OFBufferId.NO_BUFFER); |
| |
| OFActionOutput.Builder act = sw.factory().actions().buildOutput() |
| .setPort(port.getPortNo()); |
| OFAction out = act.build(); |
| packetOut.setActions(Collections.singletonList(out)); |
| this.lldpPacket.setPort(port.getPortNo().getPortNumber()); |
| this.bddpEth.setSourceMACAddress(port.getHwAddr().getBytes()); |
| |
| final byte[] bddp = this.bddpEth.serialize(); |
| packetOut.setData(bddp); |
| |
| return packetOut.build(); |
| } |
| |
| |
| private void sendMsg(final OFMessage msg) { |
| this.sw.sendMsg(msg); |
| } |
| |
| public String getName() { |
| return "LinkDiscovery " + this.sw.getStringId(); |
| } |
| |
| /* |
| * Handles an incoming LLDP packet. Creates link in topology and sends ACK |
| * to port where LLDP originated. |
| */ |
| public boolean handleLLDP(final byte[] pkt, Integer inPort) { |
| |
| short ethType = ONLabLddp.isOVXLLDP(pkt); |
| if (ethType == Ethernet.TYPE_LLDP || ethType == Ethernet.TYPE_BSN) { |
| final Integer dstPort = inPort; |
| final DPIDandPort dp = ONLabLddp.parseLLDP(pkt); |
| final OpenFlowSwitch srcSwitch = ctrl.getSwitch(new Dpid(dp.getDpid())); |
| final Integer srcPort = dp.getPort(); |
| if (srcSwitch == null) { |
| return true; |
| } |
| this.ackProbe(srcPort); |
| ConnectPoint src = new ConnectPoint( |
| DeviceId.deviceId(uri(srcSwitch.getId())), |
| PortNumber.portNumber(srcPort)); |
| |
| ConnectPoint dst = new ConnectPoint( |
| DeviceId.deviceId(uri(sw.getId())), |
| PortNumber.portNumber(dstPort)); |
| LinkDescription ld; |
| if (ethType == Ethernet.TYPE_BSN) { |
| ld = new DefaultLinkDescription(src, dst, Type.INDIRECT); |
| } else { |
| ld = new DefaultLinkDescription(src, dst, Type.DIRECT); |
| } |
| linkProvider.linkDetected(ld); |
| return true; |
| } else { |
| this.log.debug("Ignoring unknown LLDP"); |
| return false; |
| } |
| } |
| |
| private OFPortDesc findPort(Integer inPort) { |
| return ports.get(inPort); |
| } |
| |
| /** |
| * Execute this method every t milliseconds. Loops over all ports |
| * labeled as fast and sends out an LLDP. Send out an LLDP on a single slow |
| * port. |
| * |
| * @param t timeout |
| * @throws Exception |
| */ |
| @Override |
| public void run(final Timeout t) { |
| this.log.debug("sending probes"); |
| synchronized (this) { |
| final Iterator<Integer> fastIterator = this.fastPorts.iterator(); |
| while (fastIterator.hasNext()) { |
| final Integer portNumber = fastIterator.next(); |
| final int probeCount = this.portProbeCount.get(portNumber) |
| .getAndIncrement(); |
| OFPortDesc port = findPort(portNumber); |
| if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) { |
| this.log.debug("sending fast probe to port"); |
| |
| OFPacketOut pkt = this.createLLDPPacketOut(port); |
| this.sendMsg(pkt); |
| if (useBDDP) { |
| OFPacketOut bpkt = this.createBDDPPacketOut(port); |
| this.sendMsg(bpkt); |
| } |
| } else { |
| // Update fast and slow ports |
| fastIterator.remove(); |
| this.slowPorts.add(portNumber); |
| this.slowIterator = this.slowPorts.iterator(); |
| this.portProbeCount.remove(portNumber); |
| |
| // Remove link from topology |
| final OFPortDesc srcPort = port; |
| |
| ConnectPoint cp = new ConnectPoint( |
| DeviceId.deviceId(uri(sw.getId())), |
| PortNumber.portNumber(srcPort.getPortNo().getPortNumber())); |
| linkProvider.linksVanished(cp); |
| } |
| } |
| |
| // send a probe for the next slow port |
| if (this.slowPorts.size() > 0) { |
| if (!this.slowIterator.hasNext()) { |
| this.slowIterator = this.slowPorts.iterator(); |
| } |
| if (this.slowIterator.hasNext()) { |
| final int portNumber = this.slowIterator.next(); |
| this.log.debug("sending slow probe to port {}", portNumber); |
| OFPortDesc port = findPort(portNumber); |
| |
| OFPacketOut pkt = this.createLLDPPacketOut(port); |
| this.sendMsg(pkt); |
| if (useBDDP) { |
| OFPacketOut bpkt = this.createBDDPPacketOut(port); |
| this.sendMsg(bpkt); |
| } |
| |
| } |
| } |
| } |
| |
| // reschedule timer |
| timeout = Timer.getTimer().newTimeout(this, this.probeRate, |
| TimeUnit.MILLISECONDS); |
| } |
| |
| public void removeAllPorts() { |
| for (OFPortDesc port : ports.values()) { |
| removePort(port); |
| } |
| } |
| |
| public void stop() { |
| removeAllPorts(); |
| timeout.cancel(); |
| } |
| |
| } |