/* * Copyright (c) 2013 Big Switch Networks, Inc. * * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/legal/epl-v10.html * * 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.sdnplatform.netvirt.virtualrouting.internal; import java.util.HashMap; import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.List; import org.sdnplatform.devicegroup.IDeviceGroup; import org.sdnplatform.devicemanager.IDevice; import org.sdnplatform.netvirt.virtualrouting.ForwardingAction; import org.sdnplatform.netvirt.virtualrouting.GatewayNode; import org.sdnplatform.netvirt.virtualrouting.IVRouter; import org.sdnplatform.netvirt.virtualrouting.ForwardingAction.DropReason; import org.sdnplatform.packet.IPv4; import org.sdnplatform.routing.IRoutingDecision.RoutingAction; import org.sdnplatform.util.IPV4Subnet; import org.sdnplatform.util.IPV4SubnetTrie; public class VRouterImpl implements IVRouter { public enum RuleAction { PERMIT, DROP } public enum RuleEntityType { /* A higher value indicates higher priority */ HOST(50), NetVirt(40), TENANT(30), SUBNET(20), ALL(10); private int priority; private RuleEntityType(int value) { this.priority = value; } public int getPriority() { return priority; } } /** * A routing rule contains two rule entities. One for the source and one for * the dest. It consists of the type, name and an IP subnet. The name can * be a name of a tenant, a NetVirt or a host */ protected static class RuleEntity { RuleEntityType type; String name; IPV4Subnet ip; /** * Create a rule entity. Based on the 'tenantName', 'netVirtName', 'ipStr' * and 'maskStr', this function figures out the rule entity type and * priority and creates a rule entity object. * @param tenantName The tenant name * @param netVirtName The netVirt name * @param ipStr The ip address * @param maskStr The subnet mask */ public RuleEntity(String tenantName, String netVirtName, String ipStr, String maskStr) { if (maskStr != null && maskStr.equals("0.0.0.0")) { this.type = RuleEntityType.HOST; } else if (netVirtName != null) { this.type = RuleEntityType.NetVirt; } else if (tenantName != null) { this.type = RuleEntityType.TENANT; } else if (maskStr != null && maskStr.equals("255.255.255.255")) { this.type = RuleEntityType.ALL; } else { this.type = RuleEntityType.SUBNET; } switch (this.type) { case HOST: this.name = ipStr; break; case SUBNET: this.name = null; break; case ALL: this.name = null; break; case NetVirt: this.name = netVirtName; break; case TENANT: this.name = tenantName; break; } int addr; if (ipStr != null) addr = IPv4.toIPv4Address(ipStr); else addr = 0; int maskIp; if (maskStr != null) maskIp = IPv4.toIPv4Address(maskStr); else maskIp = 0; short maskBits = IPV4Subnet.invertedMaskIpToLen(maskIp); this.ip = new IPV4Subnet(addr, maskBits); } /** * @param rule The routing rule predicate * @param entity The device group * @param ip The device IP address * @return true if the entity + ip matches this routing rule * false otherwise */ public boolean entityMatches(IDeviceGroup entity, int ip) { switch (this.type) { case HOST: if (this.ip.address != ip) return false; break; case NetVirt: if (entity == null || !this.name.equals(entity.getName())) return false; break; case TENANT: if (entity == null) return false; String tenant = entity.getName().split("\\|")[0]; if (!this.name.equals(tenant)) return false; break; case SUBNET: if (!this.ip.contains(ip)) return false; break; case ALL: /* Matches */ break; } return true; } } protected static class ForwardingRule implements Comparable<ForwardingRule> { RuleEntity src; RuleEntity dst; String outIface; /* The interface to send the packet out of */ int nextHopIp; /* The next hop IP address */ RuleAction action; /* The action (permit, deny) */ String nextHopGatewayPool; /* The next hop gateway pool */ public ForwardingRule(RuleEntity src, RuleEntity dst, String outIface, String nextHopIp, RuleAction action, String nextHopGatewayPool) { this.src = src; this.dst = dst; this.outIface = outIface; if (nextHopIp != null) this.nextHopIp = IPv4.toIPv4Address(nextHopIp); else this.nextHopIp = 0; this.nextHopGatewayPool = nextHopGatewayPool; this.action = action; } /** * @param srcEntity The source device group (NetVirt) * @param srcIp The source IP address * @param dstEntity The dest device group (NetVirt) * @param dstIp the dest IP * @return true, if the source and dest match this forwarding rule * false, otherwise */ public boolean matches(IDeviceGroup srcEntity, int srcIp, IDeviceGroup dstEntity, int dstIp) { return (src.entityMatches(srcEntity, srcIp) && dst.entityMatches(dstEntity, dstIp)); } /** * Compares two rule entities * @param o1 The first entity * @param o2 The second entity * @return a negative integer if o1 compares lower to o2 (i.e. will * appear before in an ascending order * a positive integer if o1 compares higher to o2 * 0, if the two rules are equal */ private int compareEntity(RuleEntity o1, RuleEntity o2) { int rule1Priority = o1.type.getPriority(); int rule2Priority = o2.type.getPriority(); if (rule1Priority != rule2Priority) { /* If rule1 is of higher priority (higher numerical value) we * need to return a negative integer */ return rule2Priority - rule1Priority; } /* The types are of the same priority */ switch (o1.type) { case HOST: case NetVirt: case TENANT: /* This will return 0 if the two entities are the same */ return o1.name.compareTo(o2.name); case SUBNET: int rule1Mask = o1.ip.maskBits; int rule2Mask = o2.ip.maskBits; if (rule1Mask != rule2Mask) { /* A more specific IP should appear before a less * specific IP when arranged in negative order. Return * a negative integer if rule1 is more specific */ return rule2Mask - rule1Mask; } /* Both entities have the same subnet mask. Return 0 if * the two subnets are the same, otherwise their comparision * does not really matter */ return o1.ip.compareTo(o2.ip); case ALL: default: break; } return 0; } @Override public int compareTo(ForwardingRule o) { /* The source has higher priority than destination */ int result = compareEntity(this.src, o.src); if (result != 0) { /* The source comparison yielded a result */ return result; } else { return compareEntity(this.dst, o.dst); } } @Override public boolean equals(Object o) { if (o instanceof ForwardingRule) { if (compareTo((ForwardingRule)o) == 0) { return true; } else { return false; } } return false; } @Override public int hashCode() { assert false : "hashCode not designed"; return 42; // any arbitrary constant will do } } /** * Models an object that matches a forwarding rule only on the basis of the * destination host/netVirt/tenant and IP */ protected static class DestinationMatcher { /* Store all the rules with a host/netVirt/tenant specification */ SortedSet<ForwardingRule> ruleSet; /* Store all the rules with a subnet specification */ IPV4SubnetTrie<ForwardingRule> subnetTrie; public DestinationMatcher() { ruleSet = new TreeSet<ForwardingRule>(); subnetTrie = new IPV4SubnetTrie<ForwardingRule>(); } /** * Add 'rule' to the destination matcher * @param rule */ public void addRule(ForwardingRule rule) { switch (rule.dst.type) { case HOST: case NetVirt: case TENANT: ruleSet.add(rule); return; default: /* SUBNET and ALL */ subnetTrie.put(rule.dst.ip, rule); return; } } /** * Looks up the rules in this destination matcher that matches the * source and the dest. * @param srcEntity The source device group (NetVirt) * @param srcIp The source IP * @param dstEntity The dest device group (NetVirt) * @param dstIp The dest IP * @return The highest priority forwarding rule that matches * null, if no match is found */ public ForwardingRule findMatch(IDeviceGroup srcEntity, int srcIp, IDeviceGroup dstEntity, int dstIp) { for (ForwardingRule rule : ruleSet) { if (rule.matches(srcEntity, srcIp, dstEntity, dstIp)) return rule; } List<Entry<IPV4Subnet, ForwardingRule>> matchList; matchList = subnetTrie.prefixSearch(new IPV4Subnet(dstIp, (short) 32)); if (matchList == null) return null; /* The last entry in the list will be the longest prefix match */ return matchList.get(matchList.size() - 1).getValue(); } } /* Name of this router */ protected String name; /* Name of tenant owning this router */ protected String tenant; /* The map of all interfaces on this router based on their names */ protected Map<String, VRouterInterface> interfaceMap; /* The map of all entity names to interface */ protected Map<String, VRouterInterface> entityIfaceMap; /* The trie of all subnets to interfaces */ protected IPV4SubnetTrie<VRouterInterface> subnetTrie; /* The map of all tenants to outgoing interface. This is mainly used by the * system router */ protected Map<String, VRouterInterface> tenantIfaceMap; /* Store all rules having a source host name or a /32 IP address */ protected Map<String, DestinationMatcher> srcHostRuleMap; /* Store all rules having a source NetVirt name */ protected Map<String, DestinationMatcher> srcNetVirtRuleMap; /* Store all rules having a source Tenant name */ protected Map<String, DestinationMatcher> srcTenantRuleMap; /* Store all rules which have a source subnet specified (everything except * /32) */ protected IPV4SubnetTrie<DestinationMatcher> srcSubnetRuleTrie; /* The map of all gateway pools on this router based on their names */ protected Map<String, GatewayPoolImpl> gatewayPoolMap; /* The default router interface. This is the interface connecting a tenant * router to the system router */ VRouterInterface defaultIface; /* The virtual MAC for this router. Each router has a unique virtual mac */ protected Long vMac; VirtualRouterManager vRtrManager; public VRouterImpl(String name, String tenant, Long vMac, VirtualRouterManager vRtrManager) { this.name = name; this.tenant = tenant; this.defaultIface = null; interfaceMap = new HashMap<String, VRouterInterface>(); entityIfaceMap = new HashMap<String, VRouterInterface>(); tenantIfaceMap = new HashMap<String, VRouterInterface>(); srcHostRuleMap = new HashMap<String, DestinationMatcher>(); srcNetVirtRuleMap = new HashMap<String, DestinationMatcher>(); srcTenantRuleMap = new HashMap<String, DestinationMatcher>(); gatewayPoolMap = new HashMap<String, GatewayPoolImpl>(); srcSubnetRuleTrie = new IPV4SubnetTrie<DestinationMatcher>(); subnetTrie = new IPV4SubnetTrie<VRouterInterface>(); this.vMac = vMac; this.vRtrManager = vRtrManager; } @Override public String getName() { return name; } @Override public String getTenant() { return tenant; } @Override public void createInterface(String ifaceName, String netVirtName, String rtrName, boolean active) { VRouterInterface iface = new VRouterInterface(this, ifaceName, netVirtName, rtrName, active); interfaceMap.put(ifaceName, iface); String tenantName; if (netVirtName != null) { entityIfaceMap.put(netVirtName, iface); } else { entityIfaceMap.put(rtrName, iface); String[] n = rtrName.split("\\|"); tenantName = n[0]; if (tenantName.equals("system") || tenantName.equals("external")) defaultIface = iface; /* XXX The following works since there is one router per tenant */ tenantIfaceMap.put(tenantName, iface); } } @Override public void assignInterfaceAddr(String ifaceName, String ip, String subnet) throws IllegalArgumentException { int ipAddr = IPv4.toIPv4Address(ip); int subnetMask = IPv4.toIPv4Address(subnet); short maskLen = IPV4Subnet.invertedMaskIpToLen(subnetMask); VRouterInterface iface = interfaceMap.get(ifaceName); if (iface == null) { String err = new StringBuilder().append("Invalid iface "). append(ifaceName).append(" on router ").append(name). toString(); throw new IllegalArgumentException(err); } IPV4Subnet addr = new IPV4Subnet(ipAddr, maskLen); iface.addAddr(addr); subnetTrie.put(addr, iface); vRtrManager.addSubnetOwner(addr, name); vRtrManager.addIfaceIpMap(ipAddr, iface); } @Override public void addRoutingRule(String srcTenant, String srcNetVirt, String srcIp, String srcMask, String dstTenant, String dstNetVirt, String dstIp, String dstMask, String outIface, String nextHop, String action, String nextHopGatewayPool) throws IllegalArgumentException { RuleAction act; if (outIface != null) { VRouterInterface iface = interfaceMap.get(outIface); if (iface == null) { String err = new StringBuilder().append("Invalid iface "). append(outIface).append(" on router ").append(name). toString(); throw new IllegalArgumentException(err); } } RuleEntity srcEntity = new RuleEntity(srcTenant, srcNetVirt, srcIp, srcMask); RuleEntity dstEntity = new RuleEntity(dstTenant, dstNetVirt, dstIp, dstMask); if (action.equals("permit")) { act = RuleAction.PERMIT; } else { act = RuleAction.DROP; } ForwardingRule rule = new ForwardingRule(srcEntity, dstEntity, outIface, nextHop, act, nextHopGatewayPool); DestinationMatcher destMatcher; /* Find the Source map to store the rule */ switch (srcEntity.type) { case HOST: destMatcher = srcHostRuleMap.get(srcEntity.name); if (destMatcher == null) { destMatcher = new DestinationMatcher(); srcHostRuleMap.put(srcEntity.name, destMatcher); } destMatcher.addRule(rule); return; case NetVirt: destMatcher = srcNetVirtRuleMap.get(srcEntity.name); if (destMatcher == null) { destMatcher = new DestinationMatcher(); srcNetVirtRuleMap.put(srcEntity.name, destMatcher); } destMatcher.addRule(rule); return; case TENANT: destMatcher = srcTenantRuleMap.get(srcEntity.name); if (destMatcher == null) { destMatcher = new DestinationMatcher(); srcTenantRuleMap.put(srcEntity.name, destMatcher); } destMatcher.addRule(rule); return; case ALL: case SUBNET: destMatcher = srcSubnetRuleTrie.get(srcEntity.ip); if (destMatcher == null) { destMatcher = new DestinationMatcher(); srcSubnetRuleTrie.put(srcEntity.ip, destMatcher); } destMatcher.addRule(rule); return; } } @Override public void addRoutingRule(String srcTenant, String srcNetVirt, String srcIp, String srcMask, String dstTenant, String dstNetVirt, String dstIp, String dstMask, String outIface, String nextHop, String action) throws IllegalArgumentException { this.addRoutingRule(srcTenant, srcNetVirt, srcIp, srcMask, dstTenant, dstNetVirt, dstIp, dstMask, outIface, nextHop, action, null); } /* Getters for testing purposes */ protected Map<String, VRouterInterface> getInterfaceMap() { return interfaceMap; } protected Map<String, DestinationMatcher> getSrcHostRuleMap() { return srcHostRuleMap; } protected Map<String, DestinationMatcher> getSrcNetVirtRuleMap() { return srcNetVirtRuleMap; } protected Map<String, DestinationMatcher> getSrcTenantRuleMap() { return srcTenantRuleMap; } protected IPV4SubnetTrie<DestinationMatcher> getSrcSubnetRuleTrie() { return srcSubnetRuleTrie; } @Override public boolean isIfaceDown(String entityName) { VRouterInterface iface = entityIfaceMap.get(entityName); if (iface == null) { return true; } return !iface.isActive(); } /** * Creates a forwarding action based on the routing rule and the source * interface, source IP, dest IP and the dest device group. * @param rule The matching routing rule * @param srcIface The source interface * @param dst The destination device group (NetVirt) * @param srcIp The source IP * @param dstIp The dest IP * @return The forwarding action */ private ForwardingAction createForwardingAction(ForwardingRule rule, VRouterInterface srcIface, IDeviceGroup dst, int srcIp, int dstIp) { ForwardingAction action = new ForwardingAction(); /* By default the action is DROP */ if (rule == null || rule.action == RuleAction.DROP) { action.setDropReason(DropReason.DROP_RULE); action.setDropInfo(name); return action; } /* The rule action is to permit the flow */ VRouterInterface iface = null; String outIface = rule.outIface; if (outIface != null) { /* Pick the explicitly configured outgoing interface */ iface = interfaceMap.get(outIface); } else if (rule.nextHopIp != 0) { /* The user has explicitly specified a next hop IP. We will assume * that the user wants us to send the packet to this next hop and * so no further router processing is required. The packet will be * dropped later on in case the next hop does not belong to the same * NetVirt as the destination * Search for the interface directly connected to this router that * has the subnet containing the next hop IP. */ List<Entry<IPV4Subnet, VRouterInterface>> ifaceList; IPV4Subnet dstIpSubnet = new IPV4Subnet(rule.nextHopIp, (short) 32); ifaceList = subnetTrie.prefixSearch(dstIpSubnet); if (ifaceList != null && ifaceList.size() > 0) { /* The next hop IP subnet is directly connected. Select the * longest prefix match */ iface = ifaceList.get(ifaceList.size() - 1).getValue(); } } else if (dst != null) { /* Pick an appropriate NetVirt from the destination */ String dstNetVirt = dst.getName(); /* If the NetVirt is directly connected to this router, use that netVirt */ iface = entityIfaceMap.get(dstNetVirt); if (iface == null) { /* The NetVirt is not directly connected, get the tenant router */ String[] n = dstNetVirt.split("\\|"); iface = tenantIfaceMap.get(n[0]); if (iface == null) { /* If the tenant router is unavailable, use the default * interface. For tenant routers, this is the system router * interface. There is no default interface for the * system router */ iface = defaultIface; } } } else { /* This is pure L3 routing since destination device isn't known. * However the flow is permitted so we need to figure out where * to send the packet. * First check if the dest IP is for a subnet directly connected to * this router */ List<Entry<IPV4Subnet, VRouterInterface>> ifaceList; IPV4Subnet dstIpSubnet = new IPV4Subnet(dstIp, (short) 32); ifaceList = subnetTrie.prefixSearch(dstIpSubnet); if (ifaceList != null && ifaceList.size() > 0) { /* The IP subnet is directly connected. Select the longest * prefix match */ iface = ifaceList.get(ifaceList.size() - 1).getValue(); } else { /* Query the virtual router manager for the tenant router that * owns this subnet */ String ownerRtr = vRtrManager.findSubnetOwner(dstIpSubnet); if (ownerRtr != null) { String[] n = ownerRtr.split("\\|"); iface = tenantIfaceMap.get(n[0]); } if (iface == null) { /* Select the default interface */ iface = defaultIface; } } } if (iface == null) { action.setDropReason(DropReason.DST_IFACE_NOT_FOUND); action.setDropInfo(name); return action; } if (!iface.isActive()) { action.setDropReason(DropReason.IFACE_DOWN); action.setDropInfo(name + "|" + iface.getName()); return action; } if (iface == srcIface) { /* There is no known way to forward this packet */ action.setDropReason(DropReason.ROUTE_ERROR); action.setDropInfo(name + "|" + iface.getName()); return action; } if (!iface.isNetVirt()) { /* Next hop is a router */ action.setNextRtrName(iface.getvRouter()); } else { action.setDstNetVirtName(iface.getNetVirt()); Set<IPV4Subnet> addrs = iface.getAddrs(); if (addrs.size() > 0) { /* If this interface has an IP, check if we need to rewrite * source MAC */ boolean rewriteSrcMac = true; for (IPV4Subnet s : addrs) { int ipMask = ~0 << (32 - s.maskBits); if ((s.address & ipMask) == (srcIp & ipMask)) { /* The source is in the same subnet as the netVirt */ rewriteSrcMac = false; break; } } if (rewriteSrcMac) { action.setNewSrcMac(vMac); } } } action.setAction(RoutingAction.FORWARD); if (rule.nextHopIp != 0) { action.setNextHopIp(rule.nextHopIp); } else if (rule.nextHopGatewayPool != null) { action.setNextHopGatewayPool(rule.nextHopGatewayPool); action.setNextHopGatewayPoolRouter(this); } else { action.setNextHopIp(dstIp); } return action; } /* * The algorithm to look up the routing table rules is as follows: * The simple logic is find out all the rules that match the source and pick * the highest priority rule among those on the basis of the destination. * Routing rules can be one of the following types in order of priority: * HOST > NetVirt > TENANT > SUBNET > ALL * Matching the source has a higher priority than the dest. So, we start off * by looking at rules for the source host. If none are found, look for * rules that match the source NetVirt, then the tenant, then the longest prefix * for the source subnet. * If at any point we find a rule matching the source, we check if it * applies to the destination. If so, we make use of the rule. If there are * multiple such rules, we select on the basis of the destination priority */ @Override public ForwardingAction getForwardingAction(String srcIfaceEntity, IDeviceGroup src, int srcIp, IDeviceGroup dst, int dstIp) { /* Get the source interface. If a source interface is not found it means * that this entity is not connected to this router and there is a * config error */ VRouterInterface srcIface = entityIfaceMap.get(srcIfaceEntity); if (srcIface == null) { ForwardingAction action = new ForwardingAction(); action.setDropReason(DropReason.SRC_IFACE_NOT_FOUND); String info; info = name + " " + srcIfaceEntity; action.setDropInfo(info); return action; } String srcNetVirtName = src.getName(); /* The names are assumed to be of the format <tenant>|<netVirt> */ String srcTenantName = srcNetVirtName.split("\\|")[0]; String srcIpStr = IPv4.fromIPv4Address(srcIp); DestinationMatcher dm; ForwardingRule rule; dm = srcHostRuleMap.get(srcIpStr); if (dm != null) { /* Check whether destination matches */ rule = dm.findMatch(src, srcIp, dst, dstIp); if (rule != null) return createForwardingAction(rule, srcIface, dst, srcIp, dstIp); } dm = srcNetVirtRuleMap.get(srcNetVirtName); if (dm != null) { /* Check whether destination matches */ rule = dm.findMatch(src, srcIp, dst, dstIp); if (rule != null) return createForwardingAction(rule, srcIface, dst, srcIp, dstIp); } dm = srcTenantRuleMap.get(srcTenantName); if (dm != null) { /* Check whether destination matches */ rule = dm.findMatch(src, srcIp, dst, dstIp); if (rule != null) return createForwardingAction(rule, srcIface, dst, srcIp, dstIp); } List<Entry<IPV4Subnet, DestinationMatcher>> dmList; dmList = srcSubnetRuleTrie.prefixSearch(new IPV4Subnet(srcIp, (short) 32)); if (dmList != null) { ListIterator<Entry<IPV4Subnet, DestinationMatcher>> li = dmList.listIterator(dmList.size()); while (li.hasPrevious()) { /* Go in reverse order in the list since the longest prefix * match is at the end */ dm = li.previous().getValue(); rule = dm.findMatch(src, srcIp, dst, dstIp); if (rule != null) return createForwardingAction(rule, srcIface, dst, srcIp, dstIp); } } return createForwardingAction(null, srcIface, dst, srcIp, dstIp); } @Override public long getVMac(String netVirtName, int ip) { VRouterInterface iface = entityIfaceMap.get(netVirtName); if (iface == null) { /* This NetVirt is not connected to this router */ return 0; } Set<IPV4Subnet> addrs = iface.getAddrs(); for (IPV4Subnet addr : addrs) { if (addr.address == ip) { /* This interface is configured with the IP */ return vMac; } } /* This IP does not belong to this interface */ return 0; } @Override public int getRtrIp(String netVirtName, int ip) { VRouterInterface iface = entityIfaceMap.get(netVirtName); if (iface == null) { /* This NetVirt is not connected to this router */ return 0; } Set<IPV4Subnet> addrs = iface.getAddrs(); for (IPV4Subnet addr : addrs) { if (addr.contains(ip)) { /* This is the subnet to which the ip address belongs */ return addr.address; } } /* This IP does not belong to this interface */ return 0; } @Override public void createGatewayPool(String gatewayPoolName) { GatewayPoolImpl gatewayPool = new GatewayPoolImpl(gatewayPoolName, vRtrManager); gatewayPoolMap.put(gatewayPoolName, gatewayPool); } @Override public GatewayPoolImpl getGatewayPool(String gatewayPoolName) { return gatewayPoolMap.get(gatewayPoolName); } @Override public void addGatewayPoolNode(String gatewayPoolName, String ip) throws IllegalArgumentException { GatewayPoolImpl gatewayPool = gatewayPoolMap.get(gatewayPoolName); if (gatewayPool == null) { String err = new StringBuilder().append("Invalid gateway pool name ") .append(gatewayPoolName) .append(" on router ") .append(name) .append(" for adding gateway node ") .append(ip) .toString(); throw new IllegalArgumentException(err); } gatewayPool.addGatewayNode(ip); } @Override public GatewayNode getOptimalGatewayNodeInfo(String gatewayPoolName, IDevice srcDev, Short vlan) throws IllegalArgumentException { GatewayPoolImpl gatewayPool = gatewayPoolMap.get(gatewayPoolName); if (gatewayPool == null) { String err = new StringBuilder().append("Invalid gateway pool name ") .append(gatewayPoolName) .append(" on router ") .append(name).toString(); throw new IllegalArgumentException(err); } return gatewayPool.getOptimalGatewayNodeInfo(srcDev, vlan); } @Override public void removeGatewayPoolNode(String gatewayPoolName, String ip) throws IllegalArgumentException { GatewayPoolImpl gatewayPool = gatewayPoolMap.get(gatewayPoolName); if (gatewayPool == null) { String err = new StringBuilder().append("Invalid gateway pool name ") .append(gatewayPoolName) .append(" on router ") .append(name).toString(); throw new IllegalArgumentException(err); } gatewayPool.removeGatewayNode(ip); } }