/*
* 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.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.openflow.util.HexString;
import org.sdnplatform.core.ListenerContext;
import org.sdnplatform.devicemanager.IDevice;
import org.sdnplatform.devicemanager.IDeviceService;
import org.sdnplatform.devicemanager.IEntityClass;
import org.sdnplatform.flowcache.IFlowCacheService;
import org.sdnplatform.forwarding.IRewriteService;
import org.sdnplatform.linkdiscovery.ILinkDiscoveryService;
import org.sdnplatform.netvirt.core.VNS;
import org.sdnplatform.netvirt.core.NetVirtExplainPacket;
import org.sdnplatform.netvirt.core.VNSInterface;
import org.sdnplatform.netvirt.manager.INetVirtManagerService;
import org.sdnplatform.netvirt.virtualrouting.ForwardingAction;
import org.sdnplatform.netvirt.virtualrouting.GatewayNode;
import org.sdnplatform.netvirt.virtualrouting.IGatewayPool;
import org.sdnplatform.netvirt.virtualrouting.IVRouter;
import org.sdnplatform.netvirt.virtualrouting.IVirtualMacService;
import org.sdnplatform.netvirt.virtualrouting.IVirtualRoutingService;
import org.sdnplatform.netvirt.virtualrouting.VirtualMACExhaustedException;
import org.sdnplatform.netvirt.virtualrouting.ForwardingAction.DropReason;
import org.sdnplatform.packet.Ethernet;
import org.sdnplatform.packet.IPv4;
import org.sdnplatform.routing.IRoutingService;
import org.sdnplatform.routing.IRoutingDecision.RoutingAction;
import org.sdnplatform.topology.ITopologyService;
import org.sdnplatform.tunnelmanager.ITunnelManagerService;
import org.sdnplatform.util.IPV4Subnet;
import org.sdnplatform.util.IPV4SubnetTrie;
import org.sdnplatform.util.MACAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class VirtualRouterManager {
public static final long VIRTUAL_ROUTING_MAC =
Ethernet.toLong(Ethernet.toMACAddress("5C:16:C7:01:00:00"));
protected static final Logger logger =
LoggerFactory.getLogger(VirtualRouterManager.class);
public static class RoutingRuleParams {
String owner; /* The owner router name */
String srcTenant; /* The source tenant */
String srcVNS; /* The source netVirt */
String srcIp; /* The source IP */
String srcMask; /* The source subnet mask */
String dstTenant; /* The dest tenant */
String dstVNS; /* The dest netVirt */
String dstIp; /* The dest IP */
String dstMask; /* The dest subnet mask */
String outIface; /* The interface to send packets out of */
String nextHopIp; /* The next hop IP */
String action; /* The action PERMIT/DENY */
String nextHopGatewayPool; /* The next hop Gateway Pool */
}
protected static class Tenant {
String name; /* The name of this tenant */
boolean active; /* Indicates whether the tenant is active or not */
public Tenant(String name, boolean active) {
this.name = name;
this.active = active;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
}
/* Map of all tenants */
protected Map<String, Tenant> tenants;
/* Maintains a map of all router objects based on their names */
protected Map<String, IVRouter> vRouters;
/* Maintains a mapping of all NetVirts to the virtual router it connects to */
protected Map<String, IVRouter> netVirtToRouterMap;
protected IVirtualMacService vMacManager;
protected IRewriteService rewriteService;
/* Map of all the virtual MACs used by this virtual router manager */
/* XXX Using only one MAC:
protected Set<Long> vMacSet;
*/
protected IDeviceService deviceManager;
protected INetVirtManagerService netVirtManager;
protected ILinkDiscoveryService linkDiscovery;
protected ITopologyService topology;
protected IRoutingService routingService;
protected ITunnelManagerService tunnelManager;
/* The static ARP table */
protected Map<Integer, MACAddress> staticArpTable;
/* The trie of all subnets to tenant routers owning them */
protected IPV4SubnetTrie<String> subnetTrie;
/* Map of router interface IP address to the netVirt name that router interface
* is connected to. Used for testing router interface reachability
*/
protected Map<Integer, VRouterInterface> ifaceIpMap;
public VirtualRouterManager() {
tenants = new HashMap<String, Tenant>();
vRouters = new HashMap<String, IVRouter>();
netVirtToRouterMap = new HashMap<String, IVRouter>();
/* XXX using only one MAC for all virtual routers
vMacSet = Collections.
newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
*/
subnetTrie = new IPV4SubnetTrie<String>();
ifaceIpMap = new HashMap<Integer, VRouterInterface>();
}
// ***************
// Getters and Setters
// ***************
public Map<Integer, MACAddress> getStaticArpTable() {
return staticArpTable;
}
public void setStaticArpTable(Map<Integer, MACAddress> staticArpTable) {
this.staticArpTable = staticArpTable;
}
public void setNetVirtManager(INetVirtManagerService netVirtManager) {
this.netVirtManager = netVirtManager;
}
public void setvMacManager(IVirtualMacService vMacManager) {
this.vMacManager = vMacManager;
}
public void setRewriteService(IRewriteService rewriteService) {
this.rewriteService = rewriteService;
}
public void setDeviceManager(IDeviceService deviceManager) {
this.deviceManager = deviceManager;
}
public void setLinkDiscovery(ILinkDiscoveryService linkDiscovery) {
this.linkDiscovery = linkDiscovery;
}
public void setTopology(ITopologyService topology) {
this.topology = topology;
}
public ITopologyService getTopology() {
return topology;
}
public void setRoutingService(IRoutingService routingService) {
this.routingService = routingService;
}
public IRoutingService getRoutingService() {
return routingService;
}
public ITunnelManagerService getTunnelManager() {
return tunnelManager;
}
public void setTunnelManager(ITunnelManagerService tunnelManager) {
this.tunnelManager = tunnelManager;
}
// ***************
// Virtual Routing Related
// ***************
private String[] splitEntityName(String name)
throws IllegalArgumentException {
if (name == null) {
return null;
}
String[] n = name.split("\\|");
if (n.length != 2) {
String err = new StringBuilder().append("Invalid format ").
append(name).append(", expected <tenant>|<entity>").
toString();
throw new IllegalArgumentException(err);
}
return n;
}
/**
* Create a new tenant object
* @param name Name of the tenant
* @param active Whether the tenant is active or not
*/
public void createTenant(String name, boolean active) {
Tenant tenant = new Tenant(name, active);
tenants.put(name, tenant);
}
/**
* Create a virtual router
* @param rtrName The name of the router
* @param tenantName Name of the tenant to which it belongs
* @throws IllegalArgumentException if the tenant is not present
* VirtualMACExhaustedException if it is unable to acquire a virtual
* MAC
*/
public void createVirtualRouter(String rtrName, String tenantName)
throws IllegalArgumentException,
VirtualMACExhaustedException {
Tenant tenant = tenants.get(tenantName);
if (tenant == null) {
String err = new StringBuilder().append("Unknonwn tenant ").
append(tenantName).toString();
throw new IllegalArgumentException(err);
}
String name = new StringBuilder().append(tenantName).append("|").
append(rtrName).toString();
/* XXX HACK (or maybe not). We are using just one MAC for all virtual
* routers. This is to ensure that the end VMs do not see a MAC address
* change on a failover or a virtual router config change.
*/
//Long vMac = vMacManager.acquireVirtualMac();
// packets sent by virtual routing with virtual-router mac address as the
// src-mac address can come back to the controller as a packet-in (from
// bcast domain) - ensure that these packets are ignored early in the pipeline
linkDiscovery.addMACToIgnoreList(VIRTUAL_ROUTING_MAC, 0);
Long vMac = Long.valueOf(VIRTUAL_ROUTING_MAC);
IVRouter router = new VRouterImpl(name, tenantName, vMac, this);
vRouters.put(name, router);
/* XXX Using only one MAC for all routers
vMacSet.add(vMac);
*/
}
/**
* Create a router interface
* @param rtrName The name of the router owning the interface
* @param name The interface name
* @param netVirtName The netVirt name connected to the interface
* @param nextRtrName The router name connected to the interface
* @param active Whether the interface is active or not
* @throws IllegalArgumentException if the parameters are invalid
*/
public void addVRouterIface(String rtrName, String name, String netVirtName,
String nextRtrName, boolean active)
throws IllegalArgumentException {
String[] o = splitEntityName(rtrName);
String tenantName = o[0];
IVRouter router = vRouters.get(rtrName);
if (router == null) {
String err = new StringBuilder().append("Unknown router ").
append(rtrName).toString();
throw new IllegalArgumentException(err);
}
if (netVirtName != null) {
if (nextRtrName != null) {
String err = new StringBuilder().
append("Cannot specify both netVirt and router in iface ").
append(name).append(" for router ").append(rtrName).
toString();
throw new IllegalArgumentException(err);
}
String b[] = splitEntityName(netVirtName);
if (!b[0].equals(tenantName)) {
/* The netVirt and owner are in different tenants */
String err = new StringBuilder().append("Invalid tenant ").
append(b[0]).append(", expected ").append(tenantName).
toString();
throw new IllegalArgumentException(err);
}
netVirtToRouterMap.put(netVirtName, router);
} else {
if (nextRtrName == null) {
String err = new StringBuilder().
append("Must specify netVirt or router: iface ").
append(name).append(" router ").append(rtrName).
toString();
throw new IllegalArgumentException(err);
}
IVRouter nextRouter = vRouters.get(nextRtrName);
if (nextRouter == null) {
String err = new StringBuilder().append("Unknown router ").
append(nextRtrName).toString();
throw new IllegalArgumentException(err);
}
}
router.createInterface(name, netVirtName, nextRtrName, active);
}
/**
* Assign an IP address to an interface
* @param ifaceId The interface id '<tenant>|<router>|<iface>'
* @param ip The ip address
* @param subnet The subnet mask
* @throws IllegalArgumentException if the parameters are invalid
*/
public void addIfaceIp(String ifaceId, String ip, String subnet)
throws IllegalArgumentException {
String[] o = ifaceId.split("\\|");
if (o.length != 3) {
String err = new StringBuilder().append("Invalid iface name ").
append(ifaceId).toString();
throw new IllegalArgumentException(err);
}
String tenantName = o[0];
String rtrName = new StringBuilder().append(o[0]).append("|").
append(o[1]).toString();
String ifaceName = o[2];
IVRouter router = vRouters.get(rtrName);
if (router == null) {
String err = new StringBuilder().append("Unknown router ").
append(rtrName).append(" in tenant ").append(tenantName).
toString();
throw new IllegalArgumentException(err);
}
router.assignInterfaceAddr(ifaceName, ip, subnet);
}
/**
* Adds a routing rule to a router
* Atleast one of srcTenant/srcNetVirt/(srcIp + srcMask) must be specified
* Atleast one of dstTenant/dstNetVirt/(dstIp + dstMask) must be specified
* @param p The routing rule parameters
* @throws IllegalArgumentException if the parameters are invalid
*/
public void addRoutingRule(RoutingRuleParams p)
throws IllegalArgumentException {
String[] o = splitEntityName(p.owner);
String tenantName = o[0];
String rtrName = o[1];
IVRouter router = vRouters.get(p.owner);
if (router == null) {
String err = new StringBuilder().append("Unknown router ").
append(p.owner).toString();
throw new IllegalArgumentException(err);
}
IVRouter r;
if (p.srcVNS != null) {
r = netVirtToRouterMap.get(p.srcVNS);
if (r == null) {
String err = new StringBuilder().
append("NetVirt not attached to router ").append(p.srcVNS).
toString();
throw new IllegalArgumentException(err);
}
}
if (p.dstVNS != null) {
r = netVirtToRouterMap.get(p.dstVNS);
if (r == null) {
String err = new StringBuilder().
append("NetVirt not attached to router ").append(p.dstVNS).
toString();
throw new IllegalArgumentException(err);
}
}
if (p.srcTenant != null) {
Tenant tenant = tenants.get(p.srcTenant);
if (tenant == null) {
String err = new StringBuilder().append("Unknown tenant ").
append(p.srcTenant).toString();
throw new IllegalArgumentException(err);
}
}
if (p.dstTenant != null) {
Tenant tenant = tenants.get(p.dstTenant);
if (tenant == null) {
String err = new StringBuilder().append("Unknown tenant ").
append(p.dstTenant).toString();
throw new IllegalArgumentException(err);
}
}
String iface = null;
if (p.outIface != null) {
String[] i = p.outIface.split("\\|");
if (i.length != 3 || !i[0].equals(tenantName) ||
!i[1].equals(rtrName)) {
String err = new StringBuilder().append("Invalid iface ").
append(p.outIface).append(" for router ").
append(p.owner).toString();
throw new IllegalArgumentException(err);
}
iface = i[2];
}
String gwPool = null;
if (p.nextHopGatewayPool != null) {
String[] g = p.nextHopGatewayPool.split("\\|");
if (g.length != 3 || !g[0].equals(tenantName) ||
!g[1].equals(rtrName)) {
String err = new StringBuilder().append("Invalid gwPool ").
append(p.nextHopGatewayPool).append(" for router ").
append(p.owner).toString();
throw new IllegalArgumentException(err);
}
gwPool = g[2];
}
router.addRoutingRule(p.srcTenant, p.srcVNS, p.srcIp, p.srcMask,
p.dstTenant, p.dstVNS, p.dstIp, p.dstMask,
iface, p.nextHopIp, p.action,
gwPool);
}
/**
* Create a gateway pool on a router
* @param rtrName The name of the router owning the interface
* @param name The gateway pool name
* @throws IllegalArgumentException if the parameters are invalid
*/
public void addGatewayPool(String rtrName, String name)
throws IllegalArgumentException {
IVRouter router = vRouters.get(rtrName);
if (router == null) {
String err = new StringBuilder().append("Unknown router ").
append(rtrName).toString();
throw new IllegalArgumentException(err);
}
router.createGatewayPool(name);
}
/**
* Add a gateway node to a gateway pool
* @param gatewayNodeId The gateway node id of the format
* <tenant>|<router>|<gateway pool name>'
* @param ip The ip address
* @throws IllegalArgumentException if the parameters are invalid
*/
public void addGatewayPoolNode(String gatewayNodeId, String ip)
throws IllegalArgumentException {
String[] o = gatewayNodeId.split("\\|");
if (o.length != 3) {
String err = new StringBuilder().append("Invalid gateway node name ").
append(gatewayNodeId).toString();
throw new IllegalArgumentException(err);
}
String tenantName = o[0];
String rtrName = new StringBuilder().append(o[0]).append("|").
append(o[1]).toString();
String gatewayPoolName = o[2];
IVRouter router = vRouters.get(rtrName);
if (router == null) {
String err = new StringBuilder().append("Unknown router ").
append(rtrName).append(" in tenant ").append(tenantName).
toString();
throw new IllegalArgumentException(err);
}
router.addGatewayPoolNode(gatewayPoolName, ip);
}
/**
* Get the gateway pool object with given name that belongs to the
* specified router (only used for testing)
* @param rtrName name of the router
* @param gatewayPoolName name of the gateway pool
* @return gateway pool object
*/
public IGatewayPool getGatewayPool(String rtrName,
String gatewayPoolName) {
IVRouter router = vRouters.get(rtrName);
if (router == null) {
String err = new StringBuilder().append("Unknown router ").
append(rtrName).toString();
throw new IllegalArgumentException(err);
}
return router.getGatewayPool(gatewayPoolName);
}
/**
* Remove a gateway node from a gateway pool (only used for testing)
* @param gatewayNodeId The gateway node id of the format
* <tenant>|<router>|<gateway pool name>'
* @param ip The ip address
* @throws IllegalArgumentException if the parameters are invalid
*/
public void removeGatewayPoolNode(String gatewayNodeId, String ip)
throws IllegalArgumentException {
String[] o = gatewayNodeId.split("\\|");
if (o.length != 3) {
String err = new StringBuilder().append("Invalid gateway node name ").
append(gatewayNodeId).toString();
throw new IllegalArgumentException(err);
}
String tenantName = o[0];
String rtrName = new StringBuilder().append(o[0]).append("|").
append(o[1]).toString();
String gatewayPoolName = o[2];
IVRouter router = vRouters.get(rtrName);
if (router == null) {
String err = new StringBuilder().append("Unknown router ").
append(rtrName).append(" in tenant ").append(tenantName).
toString();
throw new IllegalArgumentException(err);
}
router.removeGatewayPoolNode(gatewayPoolName, ip);
}
/**
* Returns if there exists a netVirt interface in 'srcIfaces' that is allowed
* to communicate with a netVirt interface in 'dstIfaces'. This also includes
* the case where there is a common netVirt interface in the two sets
* @param srcIfaces The list of source interfaces
* @param dstIfaces The list of dest interfaces
* @param srcIp The source IP address
* @param dstIp The dest IP address
* @return true if communication between the source and dest is allowed
* false otherwise
*/
public boolean connected(List<VNSInterface> srcIfaces,
List<VNSInterface> dstIfaces, int srcIp,
int dstIp) {
VNS srcNetVirt, dstNetVirt;
if (logger.isTraceEnabled()) {
logger.trace("VirtualRouterManager: connected() called. " +
"srcIp={}, dstIp={}", IPv4.fromIPv4Address(srcIp),
IPv4.fromIPv4Address(dstIp));
}
if (srcIfaces == null || dstIfaces == null) {
if (logger.isTraceEnabled()) {
logger.trace("srcIfaces or dstIfaces is null returning false");
}
return false;
}
/*
* Check if there are two NetVirts which are matching or are configured to
* communicate by policy
*/
for (VNSInterface sface : srcIfaces) {
srcNetVirt = sface.getParentVNS();
for (VNSInterface dface : dstIfaces) {
dstNetVirt = dface.getParentVNS();
if (srcNetVirt.equals(dstNetVirt)) {
/*
* device1 and device2 are in the same NetVirt so they are
* connected
*/
if (logger.isTraceEnabled()) {
logger.trace("Source and dest are in the same NetVirt " +
"({}), returning true", srcNetVirt.getName());
}
return true;
} else {
ForwardingAction action = findRoute(srcNetVirt, srcIp, dstNetVirt,
dstIp);
if (action.getAction().equals(RoutingAction.FORWARD)) {
/* The two NetVirts are allowed to communicate by policy */
if (logger.isTraceEnabled()) {
logger.trace("Source NetVirt {} and dest netVirt {} are " +
"allowed to communicate by VRS",
srcNetVirt.getName(), dstNetVirt.getName());
}
return true;
}
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Source and dest are not connected");
}
return false;
}
/**
* Find a device based on the static ARP configuration or IP address.
* If the static ARP MAC address is the same as the original MAC address,
* then the device lookup is spared and we use the original device
* @param oldDev The original device
* @param ec The entity class to look in
* @param origMAC The original MAC address of the device
* @param vlan The vlan on the packet
* @param ip The IP address of the device.
* @return The device to use. null if no device is found
*/
protected IDevice findDevice(IDevice oldDev, IEntityClass ec,
long origMAC, short vlan, int ip) {
/* Check if there is a static ARP entry for this IP. If so, try
* to find the device associated with that MAC.
*/
MACAddress mac = staticArpTable.get(Integer.valueOf(ip));
if (mac != null) {
if (origMAC != mac.toLong()) {
/* If the origMAC is already the same as the static ARP table
* MAC, then the device lookup was already attempted by
* deviceManager, so we can skip this.
* If not, see if we can find the device with the corrected MAC.
*/
return deviceManager.findClassDevice(ec, mac.toLong(),
vlan, ip);
} else {
return oldDev;
}
} else {
/* Attempt to find the device from the IP */
Iterator<? extends IDevice> deviter =
deviceManager.queryClassDevices(ec, null, null, ip, null,
null);
while (deviter.hasNext()) {
IDevice dev = deviter.next();
Integer[] ipv4addrs = dev.getIPv4Addresses();
for (Integer ipv4addr : ipv4addrs) {
if (ipv4addr.equals(ip)) {
return dev;
}
}
}
}
return null;
}
/**
* Updates the explain packet if required
* @param cntx The listener context
* @param srcIface The source NetVirt interface chosen
* @param dstIface The dest NetVirt interface chosen
* @param retAction The return action
*/
private void updateExplainPacket(ListenerContext cntx,
VNSInterface srcIface,
VNSInterface dstIface,
ForwardingAction retAction) {
if (NetVirtExplainPacket.isExplainPktCntx(cntx)) {
NetVirtExplainPacket.ExplainPktVRouting.
ExplainPktAddVRouteToContext(cntx, srcIface, dstIface, retAction);
if (retAction.getAction() != RoutingAction.DROP) {
NetVirtExplainPacket.explainPacketSetContext(cntx,
NetVirtExplainPacket.KEY_EXPLAIN_PKT_SRC_NetVirt,
srcIface.getParentVNS().getName());
NetVirtExplainPacket.explainPacketSetContext(cntx,
NetVirtExplainPacket.KEY_EXPLAIN_PKT_DST_NetVirt,
dstIface.getParentVNS().getName());
}
}
}
/**
* Sets the appropriate rewrite actions for this packet
* @param cntx The listener context
* @param act The return forwarding action
* @param origDstMAC The original dest MAC
* @param origSrcMAC The original source MAC
* @param dst The destination device to which we are sending packets
* @param vRouterMac Indicates if this is a virtual router MAC or not
*/
private void setRewriteActions(ListenerContext cntx, ForwardingAction act,
long origDstMAC, long origSrcMAC,
IDevice dst, boolean vRouterMac) {
/* Check if the dest MAC has changed or dest MAC is a vRouter mac.
* If so, we MUST rewrite the dest MAC
*/
long newDstMAC = dst.getMACAddress();
if ((origDstMAC != newDstMAC) || vRouterMac) {
if (logger.isTraceEnabled()) {
logger.trace("Rewriting dst MAC from {} to {}",
HexString.toHexString(origDstMAC),
HexString.toHexString(newDstMAC));
}
rewriteService.setIngressDstMac(origDstMAC, newDstMAC, cntx);
/* XXX If we are rewriting the dest MAC, we need the flowmod to
* match the dest IP. Otherwise there is no way to send two
* packets both destined to the virtual router MAC to
* separate destinations.
*/
}
if (act.getNewSrcMac() != 0) {
if (logger.isTraceEnabled()) {
logger.trace("Rewriting src MAC from {} to {}",
HexString.toHexString(origSrcMAC),
HexString.toHexString(act.getNewSrcMac()));
}
rewriteService.setEgressSrcMac(origSrcMAC, act.getNewSrcMac(),
cntx);
}
if (vRouterMac) {
if (logger.isTraceEnabled()) {
logger.trace("Decrementing TTL by 1");
}
rewriteService.setTtlDecrement(1, cntx);
}
}
/**
* If the packet is destined to a tunnel loopback port mac address,
* but the destination IP does not correspond to a tunnel loopback
* port on any of the switches, we assume they have been sent to the
* tunnel loopback port by the NOF domain so that they can be
* routed to the device with the destination IP address.
* Such packets are subjected to the conventional virtual routing
* logic, with the following pre-processing on the sdnplatform ctxt:
* o Clear CONTEXT_DST_DEVICE
* o Clear CONTEXT_DST_IFACES
*
* @param cntx - listener context
* @param vlan id - vlan id of the packet
* @param dstIp - destination ip of the packet
* @return the original destination device if there was an update, null
* otherwise
*/
private IDevice
updateDestDevice(ListenerContext cntx, short vlan, int dstIp) {
IDevice srcDevice =
IDeviceService.fcStore.get(cntx,
IDeviceService.CONTEXT_SRC_DEVICE);
IDevice origDstDevice =
IDeviceService.fcStore.get(cntx,
IDeviceService.CONTEXT_DST_DEVICE);
if (origDstDevice == null) {
return null;
}
if (!tunnelManager.isTunnelEndpoint(origDstDevice)) {
return null;
}
if (dstIp == 0 ||
tunnelManager.getSwitchDpid(dstIp) != null) {
return null;
}
/*
* Note on vlan: findDevice uses vlan only if it finds a static ARP
* entry for the given IP address. Otherwise the device is found
* merely by looking up the IP address.
*/
IDevice newDstDevice = findDevice(null, srcDevice.getEntityClass(),
0, vlan, dstIp);
if (newDstDevice == null) {
if (logger.isTraceEnabled()) {
logger.trace("Failed to find dstDevice for actual DstIp {}",
dstIp);
}
}
IDeviceService.fcStore.remove(cntx,
IDeviceService.CONTEXT_DST_DEVICE);
INetVirtManagerService.bcStore.remove(cntx,
INetVirtManagerService.CONTEXT_DST_IFACES);
return origDstDevice;
}
/**
* Checks if there exists a netVirt interface for the source that is allowed
* to communicate with a netVirt interface for the dest. This also includes
* the case where there is a common netVirt interface for source and dest
* If the source and dest are allowed to communicate, it selects the netVirt
* interfaces of the highest priority. This function handles virtual routing
* and will configure rewrite manager to rewrite source and destination MACs
* as well as decrement TTL when required.
* @param origSrcMAC The source MAC of the flow
* @param origDstMAC The dest MAC of the flow
* @param vlan The vlan on the flow
* @param srcIp The source IP address
* @param dstIp The dest IP address
* @param cntx The listener context
* @return The forwarding action
*/
public ForwardingAction getForwardingAction(long origSrcMAC,
long origDstMAC, short vlan,
short ethType, int srcIp,
int dstIp,
ListenerContext cntx) {
VNS srcNetVirt = null, dstNetVirt = null;
VNSInterface srcIfaceChosen = null, dstIfaceChosen = null;
VNS srcNetVirtChosen = null, dstNetVirtChosen = null;
boolean ret = false;
IDevice src, dst, origDst, origTunnDst;
ForwardingAction retAction = new ForwardingAction();
List<VNSInterface> srcIfaces, dstIfaces, oldDstIfaces;
srcIfaces = INetVirtManagerService.bcStore.
get(cntx, INetVirtManagerService.CONTEXT_SRC_IFACES);
if (srcIfaces == null) return new ForwardingAction();
src = IDeviceService.fcStore.get(cntx,
IDeviceService.CONTEXT_SRC_DEVICE);
origTunnDst = updateDestDevice(cntx, vlan, dstIp);
origDst = IDeviceService.fcStore.get(cntx,
IDeviceService.CONTEXT_DST_DEVICE);
oldDstIfaces = INetVirtManagerService.bcStore.
get(cntx, INetVirtManagerService.CONTEXT_DST_IFACES);
dstIfaces = oldDstIfaces;
if (dstIfaces == null) {
// We may have a static arp configured for this IP. Alternately we
// may need to lookup a device based just on dst IP. The latter may
// happen when the packet comes in with dst mac = v.router mac (we never
// learn devices with src mac = v.router mac)
dst = findDevice(origDst, src.getEntityClass(), origDstMAC, vlan,
dstIp);
if (dst != null)
dstIfaces = netVirtManager.getInterfaces(dst);
} else {
dst = origDst;
}
/* Find the source and dest NetVirt with the highest priority that are
* allowed to communicate
*/
for (VNSInterface sface : srcIfaces) {
srcNetVirt = sface.getParentVNS();
if (dstIfaces != null) {
for (VNSInterface dface : dstIfaces) {
dstNetVirt = dface.getParentVNS();
if (srcNetVirt.equals(dstNetVirt)) {
/* Both devices are in the same NetVirt so they are
* connected and virtual routing is not necessary
*/
if (!ret || srcNetVirt.compareTo(srcNetVirtChosen) < 0) {
ret = true;
srcNetVirtChosen = srcNetVirt;
dstNetVirtChosen = dstNetVirt;
srcIfaceChosen = sface;
dstIfaceChosen = dface;
retAction.setNewSrcMac(0);
retAction.setNextHopIp(dstIp);
}
} else {
/* Check if the two NetVirts are allowed to communicate by
* policy configured in virtual routing
*/
ForwardingAction action;
action = findRoute(srcNetVirt, srcIp, dstNetVirt, dstIp);
if (action.getAction().equals(RoutingAction.FORWARD)) {
if (!action.getDstNetVirtName().
equals(dstNetVirt.getName())) {
/* Routing has been configured to send this
* packet to some other NetVirt possibly to a
* special device (eg. for service insertion)
*/
dstNetVirt = netVirtManager.
getVNS(action.getDstNetVirtName());
}
if (!ret || srcNetVirt.compareTo(srcNetVirtChosen) < 0 ||
(srcNetVirt.compareTo(srcNetVirtChosen) == 0 &&
dstNetVirt.compareTo(dstNetVirtChosen) < 0)) {
ret = true;
srcNetVirtChosen = srcNetVirt;
dstNetVirtChosen = dstNetVirt;
srcIfaceChosen = sface;
dstIfaceChosen = dface;
retAction.setNextHopIp(action.getNextHopIp());
retAction.setNewSrcMac(action.getNewSrcMac());
retAction.setNextHopGatewayPool(action.getNextHopGatewayPool());
retAction.setNextHopGatewayPoolRouter(action.getNextHopGatewayPoolRouter());
}
} else if (NetVirtExplainPacket.isExplainPktCntx(cntx)) {
NetVirtExplainPacket.ExplainPktVRouting.
ExplainPktAddVRouteToContext(cntx, sface, dface,
action);
}
}
}
} else {
ForwardingAction action;
/* We weren't able to find the dst interfaces. Attempt to
* perform pure L3 routing from srcNetVirt
*/
action = findRoute(srcNetVirt, srcIp, null, dstIp);
if (action.getAction().equals(RoutingAction.FORWARD)) {
if (!ret || srcNetVirt.compareTo(srcNetVirtChosen) < 0) {
ret = true;
srcNetVirtChosen = srcNetVirt;
dstNetVirtChosen = netVirtManager.getVNS(action.getDstNetVirtName());
srcIfaceChosen = sface;
dstIfaceChosen = null;
retAction.setNewSrcMac(action.getNewSrcMac());
retAction.setNextHopIp(action.getNextHopIp());
retAction.setNextHopGatewayPool(action.getNextHopGatewayPool());
retAction.setNextHopGatewayPoolRouter(action.getNextHopGatewayPoolRouter());
}
} else if (NetVirtExplainPacket.isExplainPktCntx(cntx)) {
NetVirtExplainPacket.ExplainPktVRouting.
ExplainPktAddVRouteToContext(cntx, sface, null, action);
}
}
}
/* XXX Using only one MAC for all virtual routers
boolean vRouterMac = vMacSet.contains(origDstMAC);
*/
boolean vRouterMac = false;
if (origDstMAC == VIRTUAL_ROUTING_MAC) {
vRouterMac = true;
retAction.setDestinedToVirtualRouterMac(true);
}
if (ret && !srcNetVirtChosen.equals(dstNetVirtChosen) &&
ethType == Ethernet.TYPE_IPv4) {
/* This packet has forwarding action as RoutingAction.FORWARD (from 'ret').
* Additionally this packet has been virtual routed since source and dest NetVirt are
* different.
*/
IDevice newDst = null;
if (retAction.getNextHopGatewayPool() != null) {
IVRouter router = retAction.getNextHopGatewayPoolRouter();
GatewayNode gatewayNode =
router.getOptimalGatewayNodeInfo(
retAction.getNextHopGatewayPool(),
src, Short.valueOf(vlan));
retAction.setNextHopIp((gatewayNode != null) ?
gatewayNode.getIp() : 0);
newDst = (gatewayNode != null) ? gatewayNode.getDevice(): null;
if (logger.isTraceEnabled()) {
logger.trace("GWPool Optimal node {} for Src {}",
gatewayNode,
IPv4.fromIPv4Address(srcIp));
}
}
/* This appears to be dead code because if the next hop IP is 0, then
* the forwarding action shouldn't be forwarding in the first place
*
else if (retAction.getNextHopIp() == 0) {
newDst = null;
}
*/
else if (retAction.getNextHopIp() != dstIp) {
newDst = findDevice(dst, src.getEntityClass(), origDstMAC, vlan,
retAction.getNextHopIp());
} else {
/* New destination is the same as original destination */
newDst = dst;
}
/* At this point if the destination is unknown (silent host), it is
* possible that newDst is null
*/
if (newDst == null) {
/* Device manager failed to lookup this next hop device. We do
* not have a static ARP entry for this next hop IP. We have
* attempted to find this device using the IP addr but we still
* don't know this device. This is a silent host for which we
* don't know the rewrite address. We should ARP for this but for
* now just return
*/
if (logger.isTraceEnabled()) {
logger.trace("Destination unknown after virtual routing");
}
retAction.setDropReason(DropReason.NEXT_HOP_UNKNOWN);
retAction.setDropInfo(IPv4.fromIPv4Address(retAction.getNextHopIp()));
updateExplainPacket(cntx, srcIfaceChosen, dstIfaceChosen,
retAction);
return retAction;
}
if (dstIfaceChosen == null || newDst != dst) {
/* If the next hop is different from the original dest, we need
* to look up the dest interface again
*/
dstIfaceChosen = null;
List<VNSInterface> ifaces = netVirtManager.getInterfaces(newDst);
for (VNSInterface i : ifaces) {
if (i.getParentVNS().equals(dstNetVirtChosen)) {
dstIfaceChosen = i;
break;
}
}
}
if (dstIfaceChosen == null) {
/* The new destination device is not in the NetVirt that was chosen
* return DROP for now
*/
if (logger.isTraceEnabled()) {
logger.trace("New destination does not belong to netVirt {}",
dstNetVirtChosen.getName());
}
retAction.setDropReason(DropReason.NetVirt_MISMATCH);
retAction.setDropInfo(dstNetVirtChosen.getName());
updateExplainPacket(cntx, srcIfaceChosen, null,
retAction);
return retAction;
}
setRewriteActions(cntx, retAction, origDstMAC, origSrcMAC, newDst,
vRouterMac);
if (!newDst.equals(origDst)) {
/* The next hop has changed. Update the context */
IDeviceService.fcStore.put(cntx,
IDeviceService.CONTEXT_DST_DEVICE,
newDst);
if (origTunnDst != null) {
IDeviceService.fcStore.put(cntx,
IDeviceService.CONTEXT_ORIG_DST_DEVICE,
origTunnDst);
}
}
} else if (ret && vRouterMac) {
/* The source and dest devices belong to the same NetVirt but the packet
* is destined to the virtual router.
*/
long vMac = getRtrVMac(srcNetVirtChosen, srcIp, dstIp);
retAction.setNewSrcMac(vMac);
setRewriteActions(cntx, retAction, origDstMAC, origSrcMAC, dst,
vRouterMac);
IDeviceService.fcStore.put(cntx, IDeviceService.CONTEXT_DST_DEVICE,
dst);
}
/* If the dest MAC is a virtual router MAC we MUST annotate this flow
* as a virtual routed flow in the flow cache. This is because we have
* made decisions on this flow using virtual routing
*/
if (vRouterMac || (ret && !srcNetVirtChosen.equals(dstNetVirtChosen))) {
/* Annotate the flow cache with virtual routing service */
IFlowCacheService.fcStore.
put(cntx, IFlowCacheService.FLOWCACHE_APP_INSTANCE_NAME,
IVirtualRoutingService.VRS_FLOWCACHE_NAME);
retAction.setVirtualRouted(true);
} else if (ret) {
IFlowCacheService.fcStore.
put(cntx, IFlowCacheService.FLOWCACHE_APP_INSTANCE_NAME,
srcNetVirtChosen.getName());
} else {
IFlowCacheService.fcStore.
put(cntx, IFlowCacheService.FLOWCACHE_APP_INSTANCE_NAME,
IVirtualRoutingService.VRS_FLOWCACHE_NAME);
}
if (ret) {
if (srcIfaces.size() > 1) {
srcIfaces = Collections.singletonList(srcIfaceChosen);
INetVirtManagerService.bcStore.
put(cntx, INetVirtManagerService.CONTEXT_SRC_IFACES, srcIfaces);
}
if (oldDstIfaces == null || oldDstIfaces.size() > 1) {
dstIfaces = Collections.singletonList(dstIfaceChosen);
INetVirtManagerService.bcStore.
put(cntx, INetVirtManagerService.CONTEXT_DST_IFACES, dstIfaces);
}
// Annotate netVirtName if the packetIn belongs to a NetVirt
IVirtualRoutingService.vrStore.
put(cntx, IVirtualRoutingService.NetVirt_NAME,
srcNetVirtChosen.getName());
retAction.setAction(RoutingAction.FORWARD);
updateExplainPacket(cntx, srcIfaceChosen, dstIfaceChosen,
retAction);
return retAction;
} else if (dstIfaces != null) {
/* The two sets of interfaces are not allowed to communicate */
INetVirtManagerService.bcStore.
remove(cntx, INetVirtManagerService.CONTEXT_SRC_IFACES);
INetVirtManagerService.bcStore.
remove(cntx, INetVirtManagerService.CONTEXT_DST_IFACES);
}
return retAction;
}
/**
* Look up routing table and check if the source is allowed to communicate
* with the dest via virtual routing feature. Returns the forwarding action
* if this is the case
* @param srcNetVirt The source NetVirt
* @param srcIp The source IP
* @param dstNetVirt The dest NetVirt
* @param dstIp The dest IP
* @return The forwarding action
*/
private ForwardingAction findRoute(VNS srcNetVirt, int srcIp, VNS dstNetVirt,
int dstIp) {
String entity = srcNetVirt.getName();
IVRouter vRouter = netVirtToRouterMap.get(entity);
int hopcount = 1;
ForwardingAction fAction = new ForwardingAction();
if (vRouter == null) {
fAction.setDropReason(DropReason.UNKNOWN_SRC_RTR);
fAction.setDropInfo(entity);
return fAction;
}
while (hopcount < 256) {
if (vRouter.isIfaceDown(entity)) {
fAction.setAction(RoutingAction.DROP);
fAction.setDropReason(DropReason.IFACE_DOWN);
String info;
info = vRouter.getName() + " " + entity;
fAction.setDropInfo(info);
break;
}
fAction = (ForwardingAction)
vRouter.getForwardingAction(entity, srcNetVirt, srcIp, dstNetVirt,
dstIp);
if (fAction.getAction() == RoutingAction.DROP) {
break;
}
String nextRouter = fAction.getNextRtrName();
if (nextRouter != null) {
entity = vRouter.getName();
vRouter = vRouters.get(nextRouter);
hopcount++;
} else {
/* Destination NetVirt is found */
break;
}
}
/* If the hopcount reaches 256, we hit max TTL. Drop packet.
* XXX This indicates that there is a route loop
*/
return fAction;
}
/**
* Give up all the vMacs that are used by routers of this object
* This method should be called only when this object is being destroyed
*/
public void relinquishVMacs() {
/* Only one thread can be deleting calling this function at a time */
/* XXX Only one virtual router MAC for all virtual routers
for (Long vMac : vMacSet) {
vMacManager.relinquishVirtualMAC(vMac);
}
vMacSet.clear();
*/
}
/**
* Tests whether a router IP is reachable from a source NetVirt + src IP
* combination and returns the router MAC
* The behaviour of this function is undefined if a router IP belongs to
* more than one router interfaces. However, it will always return a router
* MAC address if the source NetVirt is directly connected to the interface
* configured with the IP.
* @param srcNetVirt The source NetVirt
* @param srcIp The source IP
* @param dstIp The dest IP. This IP must belong to a router
* @return a router MAC if the router IP is reachable by the source.
* 0 otherwise
*/
public long getRtrVMac(VNS srcNetVirt, int srcIp, int dstIp) {
String srcNetVirtName = srcNetVirt.getName();
IVRouter vRouter = netVirtToRouterMap.get(srcNetVirtName);
if (vRouter == null) return 0;
long vMac = vRouter.getVMac(srcNetVirtName, dstIp);
if (vMac != 0) {
/* The dstIp is configured on an interface that is directly
* connected to the srcNetVirt
*/
return vMac;
}
/* Find the NetVirt connected to the router interface configured with the
* 'dstIp'
*/
VRouterInterface iface = ifaceIpMap.get(Integer.valueOf(dstIp));
if (iface == null) {
/* The dstIp does not belong to any router interface */
return 0;
}
String dstNetVirtName = iface.getNetVirt();
VNS dstNetVirt = netVirtManager.getVNS(dstNetVirtName);
ForwardingAction fwdAct = findRoute(srcNetVirt, srcIp, dstNetVirt, dstIp);
if (fwdAct.getAction() != RoutingAction.DROP) {
/* Check if the reverse path is allowed */
ForwardingAction revAct = findRoute(dstNetVirt, dstIp, srcNetVirt, srcIp);
if (revAct.getAction() != RoutingAction.DROP) {
/* Return the MAC address of the router the source NetVirt is
* connected to
*/
return iface.getOwner().getVMac(dstNetVirtName, dstIp);
}
}
/* Not allowed to communicate to the dest IP */
return 0;
}
/**
* Returns a virtual router IP.
* If the dstIp is a virtual router IP reachable from the srcNetVirt and srcIp,
* this function returns that IP
* Otherwise returns the virtual router IP of the interface connected to the
* source NetVirt.
* @param srcNetVirt The source NetVirt
* @param srcIp The source IP
* @param dstIp The dest IP
* @return A virtual router IP
* 0 If the interface connected to the source NetVirt does not have an
* IP or if the dstIp is a router IP that is not reachable from the
* source
*/
public int getRtrIp(VNS srcNetVirt, int srcIp, int dstIp) {
String srcNetVirtName = srcNetVirt.getName();
IVRouter vRouter = netVirtToRouterMap.get(srcNetVirtName);
if (vRouter == null) {
/* The source NetVirt is not connected to a virtual router */
return 0;
}
long vMac = vRouter.getVMac(srcNetVirtName, dstIp);
if (vMac != 0) {
/* The dstIp is configured on an interface that is directly
* connected to the srcNetVirt
*/
return dstIp;
}
VRouterInterface iface = ifaceIpMap.get(Integer.valueOf(dstIp));
if (iface != null) {
/* The dstIp belongs to a router interface */
String dstNetVirtName = iface.getNetVirt();
VNS dstNetVirt = netVirtManager.getVNS(dstNetVirtName);
ForwardingAction fwdAct = findRoute(srcNetVirt, srcIp, dstNetVirt, dstIp);
if (fwdAct.getAction() != RoutingAction.DROP) {
/* Check if the reverse path is allowed */
ForwardingAction revAct = findRoute(dstNetVirt, dstIp, srcNetVirt,
srcIp);
if (revAct.getAction() != RoutingAction.DROP) {
return dstIp;
}
}
} else {
/* The dst IP is not a virtual router IP. Return the IP on the
* interface connected to the source NetVirt
*/
return vRouter.getRtrIp(srcNetVirtName, srcIp);
}
return 0;
}
/**
* Assign ownership of an IP subnet to a router
* @param subnet The IP subnet address
* @param rtrName The router name
*/
public void addSubnetOwner(IPV4Subnet subnet, String rtrName) {
subnetTrie.put(subnet, rtrName);
}
/**
* Find the router that owns the subnet to which IP belongs
* @param ip An IP address or subnet
* @return The name of the router which owns this subnet
*/
public String findSubnetOwner(IPV4Subnet ip) {
List<Entry<IPV4Subnet, String>> ownerList;
ownerList = subnetTrie.prefixSearch(ip);
if (ownerList != null && ownerList.size() > 0) {
/* Return the longest prefix match */
return ownerList.get(ownerList.size() - 1).getValue();
} else
return null;
}
public void addIfaceIpMap(int ip, VRouterInterface iface) {
ifaceIpMap.put(Integer.valueOf(ip), iface);
}
}