/* * Copyright © 2016, 2017 Ericsson India Global Services Pvt Ltd. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.netvirt.elan.arp.responder; import java.math.BigInteger; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import java.util.stream.Collectors; import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager; import org.opendaylight.genius.mdsalutil.ActionInfo; import org.opendaylight.genius.mdsalutil.BucketInfo; import org.opendaylight.genius.mdsalutil.FlowEntity; import org.opendaylight.genius.mdsalutil.InstructionInfo; import org.opendaylight.genius.mdsalutil.MDSALUtil; import org.opendaylight.genius.mdsalutil.MatchInfo; import org.opendaylight.genius.mdsalutil.MetaDataUtil; import org.opendaylight.genius.mdsalutil.NwConstants; import org.opendaylight.genius.mdsalutil.actions.ActionDrop; import org.opendaylight.genius.mdsalutil.actions.ActionLoadIpToSpa; import org.opendaylight.genius.mdsalutil.actions.ActionLoadMacToSha; import org.opendaylight.genius.mdsalutil.actions.ActionMoveShaToTha; import org.opendaylight.genius.mdsalutil.actions.ActionMoveSourceDestinationEth; import org.opendaylight.genius.mdsalutil.actions.ActionMoveSpaToTpa; import org.opendaylight.genius.mdsalutil.actions.ActionNxLoadInPort; import org.opendaylight.genius.mdsalutil.actions.ActionNxResubmit; import org.opendaylight.genius.mdsalutil.actions.ActionSetArpOp; import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldEthernetSource; import org.opendaylight.genius.mdsalutil.instructions.InstructionApplyActions; import org.opendaylight.genius.mdsalutil.instructions.InstructionGotoTable; import org.opendaylight.genius.mdsalutil.matches.MatchArpOp; import org.opendaylight.genius.mdsalutil.matches.MatchArpTpa; import org.opendaylight.genius.mdsalutil.matches.MatchEthernetType; import org.opendaylight.genius.mdsalutil.matches.MatchMetadata; import org.opendaylight.netvirt.elanmanager.api.ElanHelper; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action; import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction; import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInput; import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInputBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdOutput; import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService; import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.GetEgressActionsForTunnelInputBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.GetEgressActionsForTunnelOutput; import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.ItmRpcService; import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.instances.ElanInstance; import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowplugin.extension.nicira.action.rev140714.add.group.input.buckets.bucket.action.action.NxActionResubmitRpcAddGroupCase; import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.common.Uint64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Arp Responder Utility Class. */ public final class ArpResponderUtil { private static final Logger LOG = LoggerFactory.getLogger(ArpResponderUtil.class); /** * A Utility class. */ private ArpResponderUtil() { } /** * Get Default ARP Responder Drop flow on the DPN. * * @param dpnId * DPN on which group flow to be installed */ public static FlowEntity getArpResponderTableMissFlow(Uint64 dpnId) { return MDSALUtil.buildFlowEntity(dpnId, NwConstants.ARP_RESPONDER_TABLE, String.valueOf(NwConstants.ARP_RESPONDER_TABLE), NwConstants.TABLE_MISS_PRIORITY, ArpResponderConstant.DROP_FLOW_NAME.value(), 0, 0, NwConstants.COOKIE_ARP_RESPONDER, new ArrayList(), Collections.singletonList(new InstructionApplyActions(Collections.singletonList(new ActionDrop())))); } /** * Get Bucket Actions for ARP Responder Group Flow. * *

* Install Default Groups, Group has 1 Bucket *

* * * @param resubmitTableId * Resubmit Flow Table Id * @return List of bucket actions */ public static List getDefaultBucketInfos(short resubmitTableId) { return Collections.singletonList( new BucketInfo(Collections.singletonList(new ActionNxResubmit(resubmitTableId)))); } /** * Get Match Criteria for the ARP Responder Flow. * *

* List of Match Criteria for ARP Responder *

* * * @param lportTag * LPort Tag * @param elanInstance * Elan Instance * @param ipAddress * Ip Address to be matched to this flow * @return List of Match criteria */ public static List getMatchCriteria(int lportTag, ElanInstance elanInstance, String ipAddress) { Uint64 metadata = ElanHelper.getElanMetadataLabel(elanInstance.getElanTag().toJava(), lportTag); Uint64 metadataMask = ElanHelper.getElanMetadataMask(); return Arrays.asList(MatchEthernetType.ARP, MatchArpOp.REQUEST, new MatchArpTpa(ipAddress, "32"), new MatchMetadata(metadata, metadataMask)); } /** * Get List of actions for ARP Responder Flows. * *

* Actions consists of all the ARP actions and Resubmit Action to table * {@link NwConstants#ELAN_BASE_TABLE} such that packets can flow ELAN Rule * * @param ipAddress * IP Address for which ARP Response packet is to be generated * @param macAddress * MacAddress for which ARP Response packet is to be generated * @return List of ARP Responder Actions actions */ private static List getActions(IInterfaceManager ifaceMgrRpcService, ItmRpcService itmRpcService, String ifName, String ipAddress, String macAddress, boolean isTunnelInterface) { AtomicInteger actionCounter = new AtomicInteger(); List actions = arpActions.apply(actionCounter, macAddress, ipAddress); actions.addAll(getEgressActionsForInterface(ifaceMgrRpcService, itmRpcService, ifName, actionCounter.get(), isTunnelInterface)); LOG.trace("Total Number of actions is {}", actionCounter); return actions; } /** * A Interface that represent lambda TriFunction. * * @param * Input type * @param * Input type * @param * Input type * @param * Return Type */ @SuppressWarnings("checkstyle:ParameterName") public interface TriFunction { /** * Apply the Action. * * @param t * Input1 * @param u * Input2 * @param s * Input3 * @return computed result */ R apply(T t, U u, S s); } /** * Lambda to apply arpAction. Inputs action counter, mac address and ip * address */ private static TriFunction> arpActions = (actionCounter, mac, ip) -> { List actions = new ArrayList<>(); Collections.addAll(actions, new ActionMoveSourceDestinationEth().buildAction(actionCounter.getAndIncrement()), new ActionSetFieldEthernetSource(new MacAddress(mac)).buildAction(actionCounter.getAndIncrement()), new ActionSetArpOp(NwConstants.ARP_REPLY).buildAction(actionCounter.getAndIncrement()), new ActionMoveShaToTha().buildAction(actionCounter.getAndIncrement()), new ActionMoveSpaToTpa().buildAction(actionCounter.getAndIncrement()), new ActionLoadMacToSha(new MacAddress(mac)).buildAction(actionCounter.getAndIncrement()), new ActionLoadIpToSpa(ip).buildAction(actionCounter.getAndIncrement()), new ActionNxLoadInPort(Uint64.valueOf(BigInteger.ZERO)).buildAction(actionCounter.getAndIncrement())); return actions; }; /** * Get instruction list for ARP responder flows. */ public static List getInterfaceInstructions(IInterfaceManager ifaceMgrRpcService, String interfaceName, String ipAddress, String macAddress, ItmRpcService itmRpcService) { List actions = ArpResponderUtil.getActions(ifaceMgrRpcService, itmRpcService, interfaceName, ipAddress, macAddress, false); return Collections.singletonList(MDSALUtil.buildApplyActionsInstruction(actions)); } /** * Get instruction list for ARP responder flows originated from ext-net e.g. * router-gw/fip.
* The split-horizon bit should be reset in order to allow traffic from * provider network to be routed back to flat/VLAN network and override the * egress table drop flow.
* In order to allow write-metadata in the ARP responder table the resubmit * action needs to be replaced with goto instruction. */ public static List getExtInterfaceInstructions(IInterfaceManager ifaceMgrRpcService, ItmRpcService itmRpcService, String extInterfaceName, String ipAddress, String macAddress) { AtomicInteger tableId = new AtomicInteger(-1); List instructions = new ArrayList<>(); List actions = getActions(ifaceMgrRpcService, itmRpcService, extInterfaceName, ipAddress, macAddress, false); actions.removeIf(v -> { org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.Action actionClass = v .getAction(); if (actionClass instanceof NxActionResubmitRpcAddGroupCase) { tableId.set(((NxActionResubmitRpcAddGroupCase) actionClass).getNxResubmit().getTable().toJava()); return true; } else { return false; } }); instructions.add(MDSALUtil.buildApplyActionsInstruction(actions, 0)); if (tableId.get() != -1) { // replace resubmit action with goto so it can co-exist with // write-metadata if ((short) tableId.get() > NwConstants.ARP_RESPONDER_TABLE) { instructions.add(new InstructionGotoTable((short) tableId.get()).buildInstruction(2)); } else { LOG.warn("Failed to insall responder flow for interface {}. Resubmit to {} can't be replaced with goto", extInterfaceName, tableId); } } return instructions; } /** * Creates Uniquely Identifiable flow Id. * * @param lportTag * LportTag of the flow * @param ipAdress * Gateway IP for which ARP Response flow to be installed * @return Unique Flow Id * * @see ArpResponderConstant#FLOW_ID_FORMAT_WITH_LPORT * @see ArpResponderConstant#FLOW_ID_FORMAT_WITHOUT_LPORT */ public static String getFlowId(int lportTag, String ipAdress) { return MessageFormat.format(ArpResponderConstant.FLOW_ID_FORMAT_WITH_LPORT.value(), NwConstants.ARP_RESPONDER_TABLE, lportTag, ipAdress); } /** * Generate Cookie per flow. * *

* Cookie is generated by Summation of * {@link NwConstants#COOKIE_ARP_RESPONDER} + 1 + lportTag + Gateway IP * * @param lportTag * Lport Tag of the flow * @param ipAddress * Gateway IP for which ARP Response flow to be installed * @return Cookie */ public static Uint64 generateCookie(int lportTag, String ipAddress) { LOG.trace("IPAddress in long {}", ipAddress); return Uint64.fromLongBits(NwConstants.COOKIE_ARP_RESPONDER.longValue() + 255 + ipTolong(ipAddress) + lportTag); } private static Uint64 buildCookie(short tableId, int arpOpType) { return Uint64.fromLongBits(NwConstants.COOKIE_ARP_RESPONDER.longValue() + 1 + tableId + arpOpType); } private static String buildFlowRef(short tableId, int arpOpType) { return (tableId == NwConstants.ARP_CHECK_TABLE ? ArpResponderConstant.FLOWID_PREFIX_FOR_ARP_CHECK.value() : ArpResponderConstant.FLOWID_PREFIX_FOR_MY_GW_MAC.value()) + tableId + NwConstants.FLOWID_SEPARATOR + (arpOpType == NwConstants.ARP_REQUEST ? "arp.request" : "arp.replay"); } public static FlowEntity createArpDefaultFlow(Uint64 dpId, short tableId, int arpOpType, Supplier> matches, Supplier> actions) { List instructions = Collections.singletonList(new InstructionApplyActions(actions.get())); return MDSALUtil.buildFlowEntity(dpId, tableId, buildFlowRef(tableId, arpOpType), NwConstants.DEFAULT_ARP_FLOW_PRIORITY, buildFlowRef(tableId, arpOpType), 0, 0, buildCookie(tableId, arpOpType), matches.get(), instructions); } /** * Get IP Address in Long from String. * * @param address * IP Address that to be converted to long * @return Long value of the IP Address */ private static long ipTolong(String address) { // Parse IP parts into an int array long[] ip = new long[4]; String[] parts = address.split("\\."); for (int i = 0; i < 4; i++) { ip[i] = Long.parseLong(parts[i]); } // Add the above IP parts into an int number representing your IP // in a 32-bit binary form long ipNumbers = 0; for (int i = 0; i < 4; i++) { ipNumbers += ip[i] << (24 - (8 * i)); } return ipNumbers; } /** * Get List of Egress Action for the VPN interface. * * @param ifaceMgrRpcService * Interface Manager RPC reference that invokes API to retrieve * Egress Action * @param ifName * VPN Interface for which Egress Action to be retrieved * @param actionCounter * Action Key * @return List of Egress Actions */ public static List getEgressActionsForInterface(IInterfaceManager ifaceMgrRpcService, ItmRpcService itmRpcService, String ifName, int actionCounter, boolean isTunnelInterface) { if (isTunnelInterface && ifaceMgrRpcService.isItmDirectTunnelsEnabled()) { try { RpcResult result = itmRpcService.getEgressActionsForTunnel(new GetEgressActionsForTunnelInputBuilder() .setIntfName(ifName).build()).get(); Map listActions = new HashMap(); if (!result.isSuccessful()) { LOG.error("getEgressActionsForInterface: RPC Call to Get egress actions for interface {} " + "returned with Errors {}", ifName, result.getErrors()); } else { listActions = ((GetEgressActionsForTunnelOutput) result.getResult()).getAction(); } return new ArrayList(listActions.values()); } catch (InterruptedException | ExecutionException e) { LOG.error("getEgressActionsForInterface: Exception when egress actions for interface {}", ifName, e); } } else { List actionInfos = ifaceMgrRpcService.getInterfaceEgressActions(ifName); AtomicInteger counter = new AtomicInteger(actionCounter); return actionInfos.stream().map(v -> v.buildAction(counter.getAndIncrement())).collect(Collectors.toList()); } return Collections.emptyList(); } /** * Uses the IdManager to retrieve ARP Responder GroupId from ELAN pool. * * @param idManager * the id manager * @return the integer */ public static Long retrieveStandardArpResponderGroupId(IdManagerService idManager) { AllocateIdInput getIdInput = new AllocateIdInputBuilder() .setPoolName(ArpResponderConstant.ELAN_ID_POOL_NAME.value()) .setIdKey(ArpResponderConstant.ARP_RESPONDER_GROUP_ID.value()).build(); try { Future> result = idManager.allocateId(getIdInput); RpcResult rpcResult = result.get(); if (rpcResult.isSuccessful()) { LOG.trace("Retrieved Group Id is {}", rpcResult.getResult().getIdValue()); return rpcResult.getResult().getIdValue().toJava(); } else { LOG.warn("RPC Call to Allocate Id returned with Errors {}", rpcResult.getErrors()); } } catch (InterruptedException | ExecutionException e) { LOG.warn("Exception when Allocating Id", e); } return 0L; } }