All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.onosproject.pim.impl.PIMInterface Maven / Gradle / Ivy

There is a newer version: 1.6.0
Show newest version
/*
 * Copyright 2015, 2016 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.onosproject.pim.impl;

import com.google.common.collect.ImmutableList;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.MacAddress;
import org.onlab.packet.PIM;
import org.onlab.packet.pim.PIMAddrUnicast;
import org.onlab.packet.pim.PIMHello;
import org.onlab.packet.pim.PIMHelloOption;
import org.onlab.packet.pim.PIMJoinPrune;
import org.onlab.packet.pim.PIMJoinPruneGroup;
import org.onosproject.incubator.net.intf.Interface;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.host.InterfaceIpAddress;
import org.onosproject.net.mcast.McastRoute;
import org.onosproject.net.packet.DefaultOutboundPacket;
import org.onosproject.net.packet.PacketService;
import org.slf4j.Logger;

import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.slf4j.LoggerFactory.getLogger;

/**
 * PIM Interface represents an ONOS Interface with IP and MAC addresses for
 * a given ConnectPoint.
 */
public final class PIMInterface {

    private final Logger log = getLogger(getClass());

    private static final int JOIN_PERIOD = 60;
    private static final double HOLD_TIME_MULTIPLIER = 3.5;

    private final PacketService packetService;

    private Interface onosInterface;
    private final TrafficTreatment outputTreatment;

    // Our hello opt holdtime
    private short holdtime = PIMHelloOption.DEFAULT_HOLDTIME;

    // Our hello opt prune delay
    private int pruneDelay = PIMHelloOption.DEFAULT_PRUNEDELAY;

    // Neighbor priority
    private int priority   = PIMHelloOption.DEFAULT_PRIORITY;

    private final int helloInterval;

    private long lastHello;

    // Our current genid
    private final int generationId;

    // The IP address of the DR
    private IpAddress drIpaddress;

    // A map of all our PIM neighbors keyed on our neighbors IP address
    private Map pimNeighbors = new ConcurrentHashMap<>();

    private Map routes = new ConcurrentHashMap<>();

    /**
     * Create a PIMInterface from an ONOS Interface.
     *
     * @param intf the ONOS Interface.
     * @param holdTime hold time
     * @param priority priority
     * @param propagationDelay propagation delay
     * @param overrideInterval override interval
     * @param packetService reference to the packet service
     */
    private PIMInterface(Interface intf,
                         int helloInterval,
                         short holdTime,
                         int priority,
                         short propagationDelay,
                         short overrideInterval,
                         PacketService packetService) {

        onosInterface = intf;
        outputTreatment = createOutputTreatment();
        this.helloInterval = helloInterval;
        this.holdtime = holdTime;
        this.packetService = packetService;
        IpAddress ourIp = getIpAddress();
        MacAddress mac = intf.mac();

        lastHello = 0;

        generationId = new Random().nextInt();

        // Create a PIM Neighbor to represent ourselves for DR election.
        PIMNeighbor us = new PIMNeighbor(ourIp, mac, holdTime, 0, priority, generationId);

        pimNeighbors.put(ourIp, us);
        drIpaddress = ourIp;
    }

    private TrafficTreatment createOutputTreatment() {
        return DefaultTrafficTreatment.builder()
                .setOutput(onosInterface.connectPoint().port())
                .build();
    }

    /**
     * Return the ONOS Interface.
     *
     * @return ONOS Interface.
     */
    public Interface getInterface() {
        return onosInterface;

    }

    /**
     * Set the ONOS Interface, it will override a previous value.
     *
     * @param intf ONOS Interface
     * @return PIM interface instance
     */
    public PIMInterface setInterface(Interface intf) {
        onosInterface = intf;
        return this;
    }

    /**
     * Get the set of IP Addresses associated with this interface.
     *
     * @return a set of Ip Addresses on this interface
     */
    public List getIpAddresses() {
        return onosInterface.ipAddressesList();
    }

    /**
     * Return a single "best" IP address.
     *
     * @return the choosen IP address or null if none
     */
    public IpAddress getIpAddress() {
        if (onosInterface.ipAddressesList().isEmpty()) {
            return null;
        }

        IpAddress ipaddr = null;
        for (InterfaceIpAddress ifipaddr : onosInterface.ipAddressesList()) {
            ipaddr = ifipaddr.ipAddress();
            break;
        }
        return ipaddr;
    }

    /**
     * Get the holdtime.
     *
     * @return the holdtime
     */
    public short getHoldtime() {
        return holdtime;
    }

    /**
     * Get the prune delay.
     *
     * @return The prune delay
     */
    public int getPruneDelay() {
        return pruneDelay;
    }

    /**
     * Get our hello priority.
     *
     * @return our priority
     */
    public int getPriority() {
        return priority;
    }

    /**
     * Get our generation ID.
     *
     * @return our generation ID
     */
    public int getGenerationId() {
        return generationId;
    }

    /**
     * Gets the neighbors seen on this interface.
     *
     * @return PIM neighbors
     */
    public Collection getNeighbors() {
        return ImmutableList.copyOf(pimNeighbors.values());
    }

    public Collection getRoutes() {
        return routes.keySet();
    }

    /**
     * Checks whether any of our neighbors have expired, and cleans up their
     * state if they have.
     */
    public void checkNeighborTimeouts() {
        Set expired = pimNeighbors.values().stream()
                // Don't time ourselves out!
                .filter(neighbor -> !neighbor.ipAddress().equals(getIpAddress()))
                .filter(neighbor -> neighbor.isExpired())
                .collect(Collectors.toSet());

        for (PIMNeighbor neighbor : expired) {
            log.info("Timing out neighbor {}", neighbor);
            pimNeighbors.remove(neighbor.ipAddress(), neighbor);
        }
    }

    /**
     * Multicast a hello message out our interface.  This hello message is sent
     * periodically during the normal PIM Neighbor refresh time, as well as a
     * result of a newly created interface.
     */
    public void sendHello() {
        if (lastHello + TimeUnit.SECONDS.toMillis(helloInterval) >
                System.currentTimeMillis()) {
            return;
        }

        lastHello = System.currentTimeMillis();

        // Create the base PIM Packet and mark it a hello packet
        PIMPacket pimPacket = new PIMPacket(PIM.TYPE_HELLO);

        // We need to set the source MAC and IPv4 addresses
        pimPacket.setSrcMacAddr(onosInterface.mac());
        pimPacket.setSrcIpAddress(Ip4Address.valueOf(getIpAddress().toOctets()));

        // Create the hello message with options
        PIMHello hello = new PIMHello();
        hello.createDefaultOptions();
        hello.addOption(PIMHelloOption.createHoldTime(holdtime));
        hello.addOption(PIMHelloOption.createPriority(priority));
        hello.addOption(PIMHelloOption.createGenID(generationId));

        // Now set the hello option payload
        pimPacket.setPIMPayload(hello);

        packetService.emit(new DefaultOutboundPacket(
                onosInterface.connectPoint().deviceId(),
                outputTreatment,
                ByteBuffer.wrap(pimPacket.getEthernet().serialize())));
    }

    /**
     * Process an incoming PIM Hello message.  There are a few things going on in
     * this method:
     * 
    *
  • We may have to create a new neighbor if one does not already exist
  • *
  • We may need to re-elect a new DR if new information is received
  • *
  • We may need to send an existing neighbor all joins if the genid changed
  • *
  • We will refresh the neighbor's timestamp
  • *
* * @param ethPkt the Ethernet packet header */ public void processHello(Ethernet ethPkt) { if (log.isTraceEnabled()) { log.trace("Received a PIM hello packet"); } // We'll need to save our neighbors MAC address MacAddress nbrmac = ethPkt.getSourceMAC(); // And we'll need to save neighbors IP Address. IPv4 iphdr = (IPv4) ethPkt.getPayload(); IpAddress srcip = IpAddress.valueOf(iphdr.getSourceAddress()); PIM pimhdr = (PIM) iphdr.getPayload(); if (pimhdr.getPimMsgType() != PIM.TYPE_HELLO) { log.error("process Hello has received a non hello packet type: " + pimhdr.getPimMsgType()); return; } // get the DR values for later calculation PIMNeighbor dr = pimNeighbors.get(drIpaddress); checkNotNull(dr); IpAddress drip = drIpaddress; int drpri = dr.priority(); // Assume we do not need to run a DR election boolean reElectDr = false; boolean genidChanged = false; PIMHello hello = (PIMHello) pimhdr.getPayload(); // Determine if we already have a PIMNeighbor PIMNeighbor nbr = pimNeighbors.getOrDefault(srcip, null); PIMNeighbor newNbr = PIMNeighbor.createPimNeighbor(srcip, nbrmac, hello.getOptions().values()); if (nbr == null) { pimNeighbors.putIfAbsent(srcip, newNbr); nbr = newNbr; } else if (!nbr.equals(newNbr)) { if (newNbr.holdtime() == 0) { // Neighbor has shut down. Remove them and clean up pimNeighbors.remove(srcip, nbr); return; } else { // Neighbor has changed one of their options. pimNeighbors.put(srcip, newNbr); nbr = newNbr; } } // Refresh this neighbor's timestamp nbr.refreshTimestamp(); /* * the election method will first determine if an election * needs to be run, if so it will run the election. The * IP address of the DR will be returned. If the IP address * of the DR is different from what we already have we know a * new DR has been elected. */ IpAddress electedIp = election(nbr, drip, drpri); if (!drip.equals(electedIp)) { // we have a new DR. drIpaddress = electedIp; } } // Run an election if we need to. Return the elected IP address. private IpAddress election(PIMNeighbor nbr, IpAddress drIp, int drPriority) { IpAddress nbrIp = nbr.ipAddress(); if (nbr.priority() > drPriority) { return nbrIp; } if (nbrIp.compareTo(drIp) > 0) { return nbrIp; } return drIp; } /** * Process an incoming PIM JoinPrune message. * * @param ethPkt the Ethernet packet header. */ public void processJoinPrune(Ethernet ethPkt) { IPv4 ip = (IPv4) ethPkt.getPayload(); checkNotNull(ip); PIM pim = (PIM) ip.getPayload(); checkNotNull(pim); PIMJoinPrune jpHdr = (PIMJoinPrune) pim.getPayload(); checkNotNull(jpHdr); /* * The Join/Prune messages are grouped by Group address. We'll walk each group address * where we will possibly have to walk a list of source address for the joins and prunes. */ Collection jpgs = jpHdr.getJoinPrunes(); for (PIMJoinPruneGroup jpg : jpgs) { IpPrefix gpfx = jpg.getGroup(); // Walk the joins first. for (IpPrefix spfx : jpg.getJoins().values()) { // We may need } for (IpPrefix spfx : jpg.getPrunes().values()) { // TODO: this is where we many need to remove multi-cast state and possibly intents. } } } public void addRoute(McastRoute route, IpAddress nextHop, MacAddress nextHopMac) { RouteData data = new RouteData(nextHop, nextHopMac); routes.put(route, data); sendJoinPrune(route, data, true); } public void removeRoute(McastRoute route) { RouteData data = routes.remove(route); if (data != null) { sendJoinPrune(route, data, false); } } public void sendJoins() { routes.entrySet().forEach(entry -> { if (entry.getValue().timestamp + TimeUnit.SECONDS.toMillis(JOIN_PERIOD) > System.currentTimeMillis()) { return; } sendJoinPrune(entry.getKey(), entry.getValue(), true); }); } private void sendJoinPrune(McastRoute route, RouteData data, boolean join) { PIMJoinPrune jp = new PIMJoinPrune(); jp.addJoinPrune(route.source().toIpPrefix(), route.group().toIpPrefix(), join); jp.setHoldTime(join ? (short) Math.floor(JOIN_PERIOD * HOLD_TIME_MULTIPLIER) : 0); jp.setUpstreamAddr(new PIMAddrUnicast(data.ipAddress.toString())); PIM pim = new PIM(); pim.setPIMType(PIM.TYPE_JOIN_PRUNE_REQUEST); pim.setPayload(jp); IPv4 ipv4 = new IPv4(); ipv4.setDestinationAddress(PIM.PIM_ADDRESS.getIp4Address().toInt()); ipv4.setSourceAddress(getIpAddress().getIp4Address().toInt()); ipv4.setProtocol(IPv4.PROTOCOL_PIM); ipv4.setTtl((byte) 1); ipv4.setDiffServ((byte) 0xc0); ipv4.setPayload(pim); Ethernet eth = new Ethernet(); eth.setSourceMACAddress(onosInterface.mac()); eth.setDestinationMACAddress(MacAddress.valueOf("01:00:5E:00:00:0d")); eth.setEtherType(Ethernet.TYPE_IPV4); eth.setPayload(ipv4); TrafficTreatment treatment = DefaultTrafficTreatment.builder() .setOutput(onosInterface.connectPoint().port()) .build(); packetService.emit(new DefaultOutboundPacket(onosInterface.connectPoint().deviceId(), treatment, ByteBuffer.wrap(eth.serialize()))); data.timestamp = System.currentTimeMillis(); } /** * Returns a builder for a PIM interface. * * @return PIM interface builder */ public static Builder builder() { return new Builder(); } /** * Builder for a PIM interface. */ public static class Builder { private Interface intf; private PacketService packetService; private int helloInterval = PIMInterfaceManager.DEFAULT_HELLO_INTERVAL; private short holdtime = PIMHelloOption.DEFAULT_HOLDTIME; private int priority = PIMHelloOption.DEFAULT_PRIORITY; private short propagationDelay = PIMHelloOption.DEFAULT_PRUNEDELAY; private short overrideInterval = PIMHelloOption.DEFAULT_OVERRIDEINTERVAL; /** * Uses the specified ONOS interface. * * @param intf ONOS interface * @return this PIM interface builder */ public Builder withInterface(Interface intf) { this.intf = checkNotNull(intf); return this; } /** * Sets the reference to the packet service. * * @param packetService packet service * @return this PIM interface builder */ public Builder withPacketService(PacketService packetService) { this.packetService = checkNotNull(packetService); return this; } /** * Users the specified hello interval. * * @param helloInterval hello interval in seconds * @return this PIM interface builder */ public Builder withHelloInterval(int helloInterval) { this.helloInterval = helloInterval; return this; } /** * Uses the specified hold time. * * @param holdTime hold time in seconds * @return this PIM interface builder */ public Builder withHoldTime(short holdTime) { this.holdtime = holdTime; return this; } /** * Uses the specified DR priority. * * @param priority DR priority * @return this PIM interface builder */ public Builder withPriority(int priority) { this.priority = priority; return this; } /** * Uses the specified propagation delay. * * @param propagationDelay propagation delay in ms * @return this PIM interface builder */ public Builder withPropagationDelay(short propagationDelay) { this.propagationDelay = propagationDelay; return this; } /** * Uses the specified override interval. * * @param overrideInterval override interval in ms * @return this PIM interface builder */ public Builder withOverrideInterval(short overrideInterval) { this.overrideInterval = overrideInterval; return this; } /** * Builds the PIM interface. * * @return PIM interface */ public PIMInterface build() { checkArgument(intf != null, "Must provide an interface"); checkArgument(packetService != null, "Must provide a packet service"); return new PIMInterface(intf, helloInterval, holdtime, priority, propagationDelay, overrideInterval, packetService); } } private static class RouteData { public final IpAddress ipAddress; public final MacAddress macAddress; public long timestamp; public RouteData(IpAddress ip, MacAddress mac) { this.ipAddress = ip; this.macAddress = mac; timestamp = System.currentTimeMillis(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy