2 * Copyright © 2016, 2017 Ericsson India Global Services Pvt Ltd. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.netvirt.elan.arp.responder;
10 import java.math.BigInteger;
11 import java.text.MessageFormat;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.concurrent.ExecutionException;
17 import java.util.concurrent.Future;
18 import java.util.concurrent.atomic.AtomicInteger;
19 import java.util.function.Supplier;
20 import java.util.stream.Collectors;
22 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
23 import org.opendaylight.genius.mdsalutil.ActionInfo;
24 import org.opendaylight.genius.mdsalutil.BucketInfo;
25 import org.opendaylight.genius.mdsalutil.FlowEntity;
26 import org.opendaylight.genius.mdsalutil.InstructionInfo;
27 import org.opendaylight.genius.mdsalutil.MDSALUtil;
28 import org.opendaylight.genius.mdsalutil.MatchInfo;
29 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
30 import org.opendaylight.genius.mdsalutil.NwConstants;
31 import org.opendaylight.genius.mdsalutil.actions.ActionDrop;
32 import org.opendaylight.genius.mdsalutil.actions.ActionLoadIpToSpa;
33 import org.opendaylight.genius.mdsalutil.actions.ActionLoadMacToSha;
34 import org.opendaylight.genius.mdsalutil.actions.ActionMoveShaToTha;
35 import org.opendaylight.genius.mdsalutil.actions.ActionMoveSourceDestinationEth;
36 import org.opendaylight.genius.mdsalutil.actions.ActionMoveSpaToTpa;
37 import org.opendaylight.genius.mdsalutil.actions.ActionNxLoadInPort;
38 import org.opendaylight.genius.mdsalutil.actions.ActionNxResubmit;
39 import org.opendaylight.genius.mdsalutil.actions.ActionSetArpOp;
40 import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldEthernetSource;
41 import org.opendaylight.genius.mdsalutil.instructions.InstructionApplyActions;
42 import org.opendaylight.genius.mdsalutil.instructions.InstructionGotoTable;
43 import org.opendaylight.genius.mdsalutil.matches.MatchArpOp;
44 import org.opendaylight.genius.mdsalutil.matches.MatchArpTpa;
45 import org.opendaylight.genius.mdsalutil.matches.MatchEthernetType;
46 import org.opendaylight.genius.mdsalutil.matches.MatchMetadata;
47 import org.opendaylight.netvirt.elanmanager.api.ElanHelper;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInput;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInputBuilder;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdOutput;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.GetEgressActionsForTunnelInputBuilder;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.GetEgressActionsForTunnelOutput;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.ItmRpcService;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.instances.ElanInstance;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowplugin.extension.nicira.action.rev140714.add.group.input.buckets.bucket.action.action.NxActionResubmitRpcAddGroupCase;
60 import org.opendaylight.yangtools.yang.common.RpcResult;
61 import org.opendaylight.yangtools.yang.common.Uint64;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
66 * Arp Responder Utility Class.
68 public final class ArpResponderUtil {
70 private static final Logger LOG = LoggerFactory.getLogger(ArpResponderUtil.class);
75 private ArpResponderUtil() {
79 * Get Default ARP Responder Drop flow on the DPN.
82 * DPN on which group flow to be installed
84 public static FlowEntity getArpResponderTableMissFlow(Uint64 dpnId) {
85 return MDSALUtil.buildFlowEntity(dpnId, NwConstants.ARP_RESPONDER_TABLE,
86 String.valueOf(NwConstants.ARP_RESPONDER_TABLE), NwConstants.TABLE_MISS_PRIORITY,
87 ArpResponderConstant.DROP_FLOW_NAME.value(), 0, 0, NwConstants.COOKIE_ARP_RESPONDER,
88 new ArrayList<MatchInfo>(),
89 Collections.singletonList(new InstructionApplyActions(Collections.singletonList(new ActionDrop()))));
93 * Get Bucket Actions for ARP Responder Group Flow.
96 * Install Default Groups, Group has 1 Bucket
99 * <li>Resubmit to Table {@link NwConstants#ARP_RESPONDER_TABLE}, for ARP
100 * Auto response from DPN itself</li>
103 * @param resubmitTableId
104 * Resubmit Flow Table Id
105 * @return List of bucket actions
107 public static List<BucketInfo> getDefaultBucketInfos(short resubmitTableId) {
108 return Collections.singletonList(
109 new BucketInfo(Collections.singletonList(new ActionNxResubmit(resubmitTableId))));
113 * Get Match Criteria for the ARP Responder Flow.
116 * List of Match Criteria for ARP Responder
119 * <li>Packet is ARP</li>
120 * <li>Packet is ARP Request</li>
121 * <li>The ARP packet is requesting for Gateway IP</li>
122 * <li>Metadata which is generated by using Service
123 * Index({@link NwConstants#L3VPN_SERVICE_INDEX}) Lport Tag
124 * ({@link MetaDataUtil#METADATA_MASK_LPORT_TAG}) and VRF
125 * ID({@link MetaDataUtil#METADATA_MASK_VRFID})</li>
130 * @param elanInstance
133 * Ip Address to be matched to this flow
134 * @return List of Match criteria
136 public static List<MatchInfo> getMatchCriteria(int lportTag, ElanInstance elanInstance,
139 Uint64 metadata = ElanHelper.getElanMetadataLabel(elanInstance.getElanTag().toJava(), lportTag);
140 Uint64 metadataMask = ElanHelper.getElanMetadataMask();
141 return Arrays.asList(MatchEthernetType.ARP, MatchArpOp.REQUEST, new MatchArpTpa(ipAddress, "32"),
142 new MatchMetadata(metadata, metadataMask));
147 * Get List of actions for ARP Responder Flows.
150 * Actions consists of all the ARP actions and Resubmit Action to table
151 * {@link NwConstants#ELAN_BASE_TABLE} such that packets can flow ELAN Rule
154 * IP Address for which ARP Response packet is to be generated
156 * MacAddress for which ARP Response packet is to be generated
157 * @return List of ARP Responder Actions actions
159 private static List<Action> getActions(IInterfaceManager ifaceMgrRpcService, ItmRpcService itmRpcService,
160 String ifName, String ipAddress, String macAddress,
161 boolean isTunnelInterface) {
163 AtomicInteger actionCounter = new AtomicInteger();
164 List<Action> actions = arpActions.apply(actionCounter, macAddress, ipAddress);
165 actions.addAll(getEgressActionsForInterface(ifaceMgrRpcService, itmRpcService, ifName, actionCounter.get(),
167 LOG.trace("Total Number of actions is {}", actionCounter);
173 * A Interface that represent lambda TriFunction.
184 @SuppressWarnings("checkstyle:ParameterName")
185 public interface TriFunction<T, U, S, R> {
195 * @return computed result
197 R apply(T t, U u, S s);
201 * Lambda to apply arpAction. Inputs action counter, mac address and ip
204 private static TriFunction<AtomicInteger, String, String, List<Action>> arpActions = (actionCounter, mac, ip) -> {
205 List<Action> actions = new ArrayList<>();
206 Collections.addAll(actions, new ActionMoveSourceDestinationEth().buildAction(actionCounter.getAndIncrement()),
207 new ActionSetFieldEthernetSource(new MacAddress(mac)).buildAction(actionCounter.getAndIncrement()),
208 new ActionSetArpOp(NwConstants.ARP_REPLY).buildAction(actionCounter.getAndIncrement()),
209 new ActionMoveShaToTha().buildAction(actionCounter.getAndIncrement()),
210 new ActionMoveSpaToTpa().buildAction(actionCounter.getAndIncrement()),
211 new ActionLoadMacToSha(new MacAddress(mac)).buildAction(actionCounter.getAndIncrement()),
212 new ActionLoadIpToSpa(ip).buildAction(actionCounter.getAndIncrement()),
213 new ActionNxLoadInPort(Uint64.valueOf(BigInteger.ZERO)).buildAction(actionCounter.getAndIncrement()));
219 * Get instruction list for ARP responder flows.
221 public static List<Instruction> getInterfaceInstructions(IInterfaceManager ifaceMgrRpcService, String interfaceName,
222 String ipAddress, String macAddress, ItmRpcService itmRpcService) {
223 List<Action> actions = ArpResponderUtil.getActions(ifaceMgrRpcService, itmRpcService, interfaceName, ipAddress,
225 return Collections.singletonList(MDSALUtil.buildApplyActionsInstruction(actions));
229 * Get instruction list for ARP responder flows originated from ext-net e.g.
231 * The split-horizon bit should be reset in order to allow traffic from
232 * provider network to be routed back to flat/VLAN network and override the
233 * egress table drop flow.<br>
234 * In order to allow write-metadata in the ARP responder table the resubmit
235 * action needs to be replaced with goto instruction.
237 public static List<Instruction> getExtInterfaceInstructions(IInterfaceManager ifaceMgrRpcService,
238 ItmRpcService itmRpcService,
239 String extInterfaceName, String ipAddress,
241 AtomicInteger tableId = new AtomicInteger(-1);
242 List<Instruction> instructions = new ArrayList<>();
243 List<Action> actions = getActions(ifaceMgrRpcService, itmRpcService, extInterfaceName, ipAddress, macAddress,
245 actions.removeIf(v -> {
246 org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.Action actionClass = v
248 if (actionClass instanceof NxActionResubmitRpcAddGroupCase) {
249 tableId.set(((NxActionResubmitRpcAddGroupCase) actionClass).getNxResubmit().getTable().toJava());
256 instructions.add(MDSALUtil.buildApplyActionsInstruction(actions, 0));
258 if (tableId.get() != -1) {
259 // replace resubmit action with goto so it can co-exist with
261 if ((short) tableId.get() > NwConstants.ARP_RESPONDER_TABLE) {
262 instructions.add(new InstructionGotoTable((short) tableId.get()).buildInstruction(2));
264 LOG.warn("Failed to insall responder flow for interface {}. Resubmit to {} can't be replaced with goto",
265 extInterfaceName, tableId);
273 * Creates Uniquely Identifiable flow Id.
276 * LportTag of the flow
278 * Gateway IP for which ARP Response flow to be installed
279 * @return Unique Flow Id
281 * @see ArpResponderConstant#FLOW_ID_FORMAT_WITH_LPORT
282 * @see ArpResponderConstant#FLOW_ID_FORMAT_WITHOUT_LPORT
284 public static String getFlowId(int lportTag, String ipAdress) {
285 return MessageFormat.format(ArpResponderConstant.FLOW_ID_FORMAT_WITH_LPORT.value(),
286 NwConstants.ARP_RESPONDER_TABLE, lportTag, ipAdress);
290 * Generate Cookie per flow.
293 * Cookie is generated by Summation of
294 * {@link NwConstants#COOKIE_ARP_RESPONDER} + 1 + lportTag + Gateway IP
297 * Lport Tag of the flow
299 * Gateway IP for which ARP Response flow to be installed
302 public static Uint64 generateCookie(int lportTag, String ipAddress) {
303 LOG.trace("IPAddress in long {}", ipAddress);
304 return Uint64.fromLongBits(NwConstants.COOKIE_ARP_RESPONDER.longValue()
305 + 255 + ipTolong(ipAddress) + lportTag);
308 private static Uint64 buildCookie(short tableId, int arpOpType) {
309 return Uint64.fromLongBits(NwConstants.COOKIE_ARP_RESPONDER.longValue()
310 + 1 + tableId + arpOpType);
313 private static String buildFlowRef(short tableId, int arpOpType) {
314 return (tableId == NwConstants.ARP_CHECK_TABLE
315 ? ArpResponderConstant.FLOWID_PREFIX_FOR_ARP_CHECK.value()
316 : ArpResponderConstant.FLOWID_PREFIX_FOR_MY_GW_MAC.value()) + tableId + NwConstants.FLOWID_SEPARATOR
317 + (arpOpType == NwConstants.ARP_REQUEST ? "arp.request" : "arp.replay");
320 public static FlowEntity createArpDefaultFlow(Uint64 dpId, short tableId, int arpOpType,
321 Supplier<List<MatchInfo>> matches, Supplier<List<ActionInfo>> actions) {
323 List<InstructionInfo> instructions = Collections.singletonList(new InstructionApplyActions(actions.get()));
324 return MDSALUtil.buildFlowEntity(dpId, tableId, buildFlowRef(tableId, arpOpType),
325 NwConstants.DEFAULT_ARP_FLOW_PRIORITY, buildFlowRef(tableId, arpOpType), 0, 0,
326 buildCookie(tableId, arpOpType), matches.get(), instructions);
330 * Get IP Address in Long from String.
333 * IP Address that to be converted to long
334 * @return Long value of the IP Address
336 private static long ipTolong(String address) {
338 // Parse IP parts into an int array
339 long[] ip = new long[4];
340 String[] parts = address.split("\\.");
342 for (int i = 0; i < 4; i++) {
343 ip[i] = Long.parseLong(parts[i]);
345 // Add the above IP parts into an int number representing your IP
346 // in a 32-bit binary form
348 for (int i = 0; i < 4; i++) {
349 ipNumbers += ip[i] << (24 - (8 * i));
356 * Get List of Egress Action for the VPN interface.
358 * @param ifaceMgrRpcService
359 * Interface Manager RPC reference that invokes API to retrieve
362 * VPN Interface for which Egress Action to be retrieved
363 * @param actionCounter
365 * @return List of Egress Actions
367 public static List<Action> getEgressActionsForInterface(IInterfaceManager ifaceMgrRpcService,
368 ItmRpcService itmRpcService, String ifName,
369 int actionCounter, boolean isTunnelInterface) {
370 if (isTunnelInterface && ifaceMgrRpcService.isItmDirectTunnelsEnabled()) {
372 RpcResult result = itmRpcService.getEgressActionsForTunnel(new GetEgressActionsForTunnelInputBuilder()
373 .setIntfName(ifName).build()).get();
374 List<Action> listActions = new ArrayList<>();
375 if (!result.isSuccessful()) {
376 LOG.error("getEgressActionsForInterface: RPC Call to Get egress actions for interface {} "
377 + "returned with Errors {}", ifName, result.getErrors());
379 listActions = ((GetEgressActionsForTunnelOutput) result.getResult()).getAction();
382 } catch (InterruptedException | ExecutionException e) {
383 LOG.error("getEgressActionsForInterface: Exception when egress actions for interface {}", ifName, e);
386 List<ActionInfo> actionInfos = ifaceMgrRpcService.getInterfaceEgressActions(ifName);
387 AtomicInteger counter = new AtomicInteger(actionCounter);
388 return actionInfos.stream().map(v -> v.buildAction(counter.getAndIncrement())).collect(Collectors.toList());
390 return Collections.emptyList();
394 * Uses the IdManager to retrieve ARP Responder GroupId from ELAN pool.
398 * @return the integer
400 public static Long retrieveStandardArpResponderGroupId(IdManagerService idManager) {
402 AllocateIdInput getIdInput = new AllocateIdInputBuilder()
403 .setPoolName(ArpResponderConstant.ELAN_ID_POOL_NAME.value())
404 .setIdKey(ArpResponderConstant.ARP_RESPONDER_GROUP_ID.value()).build();
407 Future<RpcResult<AllocateIdOutput>> result = idManager.allocateId(getIdInput);
408 RpcResult<AllocateIdOutput> rpcResult = result.get();
409 if (rpcResult.isSuccessful()) {
410 LOG.trace("Retrieved Group Id is {}", rpcResult.getResult().getIdValue());
411 return rpcResult.getResult().getIdValue().toJava();
413 LOG.warn("RPC Call to Allocate Id returned with Errors {}", rpcResult.getErrors());
415 } catch (InterruptedException | ExecutionException e) {
416 LOG.warn("Exception when Allocating Id", e);