| /* |
| * 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<IpPrefix, McastRouteGroup> mrib4; |
| private final Map<IpPrefix, McastRouteGroup> mrib6; |
| private static McastRouteTable instance = null; |
| |
| private Boolean ipv6Enabled = false; |
| |
| /** |
| * Create the two v4 & v6 tables. |
| */ |
| private McastRouteTable() { |
| mrib4 = new ConcurrentHashMap<IpPrefix, McastRouteGroup>(); |
| if (ipv6Enabled) { |
| mrib6 = new ConcurrentHashMap<IpPrefix, McastRouteGroup>(); |
| } 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<IpPrefix, McastRouteGroup> getMrib4() { |
| return mrib4; |
| } |
| |
| /** |
| * Get the IPv6 MRIB. |
| * |
| * @return Return the set of prefix keyed McastGroups |
| */ |
| public Map<IpPrefix, McastRouteGroup> 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 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; |
| } |
| } |