/* * Copyright 2015 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.mfwd.impl; import org.apache.felix.scr.annotations.Service; import org.onlab.packet.IpPrefix; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import static com.google.common.base.Preconditions.checkNotNull; /** * The Mcast Route Table holds all multicast state for the controller. * * State for IPv4 and IPv6 are maintained. The tables are sets of McastRouteGroup * structures that represent (*, G) state with a series of egress ConnectPoints. * Each (*, G) may also have a set of (S, G) that may have there own set of * ingress and egress ConnectPoints. * * TODO: perhaps should probably create two separate singleton for IPv4 and IPv6 respectively. */ @Service(value = org.onosproject.mfwd.impl.McastRouteTable.class) public final class McastRouteTable { /* * Create a map of the McastGroups indexed by the multicast group prefix. * We may choose to change the map data structure in to some form a radix trie * depending on the type of real world usage we see. */ private final Map mrib4; private final Map mrib6; private static McastRouteTable instance = null; private Boolean ipv6Enabled = false; /** * Create the two v4 & v6 tables. */ private McastRouteTable() { mrib4 = new ConcurrentHashMap(); if (ipv6Enabled) { mrib6 = new ConcurrentHashMap(); } else { mrib6 = null; } } /** * Get the single instance of this multicast group address. * * @return the multicast route table */ public static McastRouteTable getInstance() { if (instance == null) { instance = new McastRouteTable(); } return instance; } /** * Get the IPv4 MRIB. * * @return the IPv4 MRIB */ public Map getMrib4() { return mrib4; } /** * Get the IPv6 MRIB. * * @return Return the set of prefix keyed McastGroups */ public Map getMrib6() { return mrib6; } /** * Save the McastRouteGroup in the address family appropriate mrib. * * @param group The McastRouteGroup to save */ private void storeGroup(McastRouteGroup group) { if (group.isIp4()) { mrib4.put(group.getGaddr(), group); } else if (group.isIp6() && ipv6Enabled) { mrib6.put(group.getGaddr(), group); } } /** * Remove the group. * * @param group the group to be removed */ private void removeGroup(McastRouteGroup group) { IpPrefix gpfx = group.getGaddr(); if (gpfx.isIp4()) { mrib4.remove(gpfx); } else if (gpfx.isIp6() && ipv6Enabled) { mrib6.remove(gpfx); } } /** * Add a multicast route to the MRIB. This function will. * * @param saddr source address * or x.x.x.x or x.x.x.x/y * @param gaddr group address x.x.x.x or x.x.x.x/y * @return the multicast route */ public McastRouteBase addRoute(String saddr, String gaddr) { IpPrefix gpfx = IpPrefix.valueOf(gaddr); IpPrefix spfx = IpPrefix.valueOf(0, 0); if (saddr != null && !saddr.equals("*")) { spfx = IpPrefix.valueOf(saddr); } return addRoute(spfx, gpfx); } /** * Add a multicast route to the MRIB. This function will store either * (S, G) or (*, G) in the mrib if an entry does not already exist. If * an entry does exist it is returned to the caller. * * Every (S, G) is stored as part of it's parent group entry which also represents * (*, G) routes. In the case of a (S, G) we will also create the (*, G) entry if needed * then save the (S, G) to the (*, G). * * @param spfx the source prefix * @param gpfx the group prefix * @return the resulting McastRouteSource or McastRouteGroup accordingly. */ public McastRouteBase addRoute(IpPrefix spfx, IpPrefix gpfx) { /** * If a group route (*, g) does not exist we will need to make so we * can start attaching our sources to the group entry. */ McastRouteGroup group = findMcastGroup(gpfx); if (group == null) { group = new McastRouteGroup(gpfx); // Save it for later if (gpfx.isIp4()) { this.mrib4.put(gpfx, group); } else if (gpfx.isIp6() && ipv6Enabled) { this.mrib6.put(gpfx, group); } } /** * If the source prefix length is 0 then we have our (*, g) entry, we can * just return now. */ if (spfx.prefixLength() == 0) { return group; } // See if the source already exists. If so just return it. McastRouteSource source = group.findSource(spfx); if (source != null) { return source; } /** * We have the group but no source. We need to create the source then add it * to the group. */ source = new McastRouteSource(spfx, gpfx); // Have the source save it's parent source.setGroup(group); // Save this source as part of this group group.addSource(source); return source; } /** * Delete a specific egress from the MRIB. * * @param saddr source address * or x.x.x.x or x.x.x.x/y * @param gaddr group address x.x.x.x or x.x.x.x/y * @param egress group address x.x.x.x or x.x.x.x/y * @return boolean if egress was deleted */ public boolean removeEgress(String saddr, String gaddr, String egress) { IpPrefix gpfx = IpPrefix.valueOf(gaddr); IpPrefix spfx = IpPrefix.valueOf(0, 0); if (saddr != null && !saddr.equals("*")) { spfx = IpPrefix.valueOf(saddr); } McastRouteSource src = (McastRouteSource) findBestMatch(spfx, gpfx); boolean removed = src.removeEgressPoint(egress); if (removed) { src.setIntent(); } return removed; } /** * Delete a multicast route from the MRIB. * * @param saddr source address * or x.x.x.x or x.x.x.x/y * @param gaddr group address x.x.x.x or x.x.x.x/y */ public void removeRoute(String saddr, String gaddr) { IpPrefix gpfx = IpPrefix.valueOf(gaddr); IpPrefix spfx = IpPrefix.valueOf(0, 0); if (saddr != null && !saddr.equals("*")) { spfx = IpPrefix.valueOf(saddr); } removeRoute(spfx, gpfx); } /** * Remove a multicast route. * * @param spfx the source prefix * @param gpfx the group prefix */ public void removeRoute(IpPrefix spfx, IpPrefix gpfx) { /** * If a group route (*, g) does not exist we will need to make so we * can start attaching our sources to the group entry. */ McastRouteGroup group = findMcastGroup(gpfx); if (group == null) { // The group does not exist, we can't remove it. return; } /** * If the source prefix length is 0 then we have a (*, g) entry, which * means we will remove this group and all of it's sources. We will * also withdraw it's intent if need be. */ if (spfx.prefixLength() > 0) { group.removeSource(spfx); /* * Now a little house keeping. If this group has no more sources * nor egress connectPoints git rid of it. */ if (group.getSources().size() == 0 && group.getEgressPoints().size() == 0) { removeGroup(group); } } else { // Group remove has been explicitly requested. group.removeSources(); group.withdrawIntent(); removeGroup(group); } } /** * Find the specific multicast group entry. * * @param group the group address * @return McastRouteGroup the multicast (*, G) group route */ public McastRouteGroup findMcastGroup(IpPrefix group) { McastRouteGroup g = null; if (group.isIp4()) { g = mrib4.get(group); } else if (group.isIp6() && ipv6Enabled) { g = mrib6.get(group); } return g; } /** * Find the multicast (S, G) entry if it exists. * * @param saddr the source address * @param gaddr the group address * @return The multicast source route entry if it exists, null if it does not. */ public McastRouteSource findMcastSource(IpPrefix saddr, IpPrefix gaddr) { McastRouteGroup grp = findMcastGroup(checkNotNull(gaddr)); if (grp == null) { return null; } return grp.findSource(saddr); } /** * This will first look up a Group entry. If no group entry was found null will * be returned. If the group entry has been found we will then look up the (s, g) entry. * If the (s, g) entry has been found, that will be returned. If no (s, g) was found * the (*, g) group entry will be returned. * * @param saddr the source address * @param gaddr the group address * @return return the best matching McastRouteSource or McastRouteGroup */ public McastRoute findBestMatch(IpPrefix saddr, IpPrefix gaddr) { McastRouteGroup grp = this.findMcastGroup(checkNotNull(gaddr)); if (grp == null) { return null; } // Found a group now look for a source McastRouteSource src = grp.findSource(checkNotNull(saddr)); if (src == null) { return grp; } return src; } /** * Print out the multicast route table in it's entirety. * * TODO: Eventually we will have to implement paging and how to handle large tables. * @return String */ public String printMcastRouteTable() { String out = this.toString() + "\n"; for (McastRouteGroup grp : mrib4.values()) { out += grp.toString() + "\n"; for (McastRouteSource src : grp.getSources().values()) { out += src.toString() + "\n"; } } return out; } /** * Print out a summary of groups in the MRIB. * * @return String */ public String toString() { String out = "Mcast Route Table: "; out += mrib4.size() + " IPv4 Multicast Groups\n"; if (ipv6Enabled) { out += mrib6.size() + " IPv6 Multicast Groups\n"; } return out; } }