ARP flow fails due missing group
[netvirt.git] / elanmanager / api / src / main / java / org / opendaylight / netvirt / elan / arp / responder / ArpResponderUtil.java
1 /*
2  * Copyright © 2016, 2017 Ericsson India Global Services Pvt Ltd. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netvirt.elan.arp.responder;
9
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;
21
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.GroupEntity;
27 import org.opendaylight.genius.mdsalutil.InstructionInfo;
28 import org.opendaylight.genius.mdsalutil.MDSALUtil;
29 import org.opendaylight.genius.mdsalutil.MatchInfo;
30 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
31 import org.opendaylight.genius.mdsalutil.NwConstants;
32 import org.opendaylight.genius.mdsalutil.actions.ActionDrop;
33 import org.opendaylight.genius.mdsalutil.actions.ActionLoadIpToSpa;
34 import org.opendaylight.genius.mdsalutil.actions.ActionLoadMacToSha;
35 import org.opendaylight.genius.mdsalutil.actions.ActionMoveShaToTha;
36 import org.opendaylight.genius.mdsalutil.actions.ActionMoveSourceDestinationEth;
37 import org.opendaylight.genius.mdsalutil.actions.ActionMoveSpaToTpa;
38 import org.opendaylight.genius.mdsalutil.actions.ActionNxLoadInPort;
39 import org.opendaylight.genius.mdsalutil.actions.ActionNxResubmit;
40 import org.opendaylight.genius.mdsalutil.actions.ActionSetArpOp;
41 import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldEthernetSource;
42 import org.opendaylight.genius.mdsalutil.instructions.InstructionApplyActions;
43 import org.opendaylight.genius.mdsalutil.instructions.InstructionGotoTable;
44 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
45 import org.opendaylight.genius.mdsalutil.matches.MatchArpOp;
46 import org.opendaylight.genius.mdsalutil.matches.MatchArpTpa;
47 import org.opendaylight.genius.mdsalutil.matches.MatchEthernetType;
48 import org.opendaylight.genius.mdsalutil.matches.MatchMetadata;
49 import org.opendaylight.netvirt.elanmanager.api.ElanHelper;
50 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInput;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInputBuilder;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdOutput;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.GetEgressActionsForTunnelInputBuilder;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.GetEgressActionsForTunnelOutput;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.ItmRpcService;
61 import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.GroupTypes;
62 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.elan.rev150602.elan.instances.ElanInstance;
63 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowplugin.extension.nicira.action.rev140714.add.group.input.buckets.bucket.action.action.NxActionResubmitRpcAddGroupCase;
64 import org.opendaylight.yangtools.yang.common.RpcResult;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67
68 /**
69  * Arp Responder Utility Class.
70  */
71 public final class ArpResponderUtil {
72
73     private static final Logger LOG = LoggerFactory.getLogger(ArpResponderUtil.class);
74
75     /**
76      * A Utility class.
77      */
78     private ArpResponderUtil() {
79     }
80
81     /**
82      * Install Group flow on the DPN.
83      *
84      * @param mdSalManager
85      *            Reference of MDSAL API RPC that provides API for installing
86      *            group flow
87      * @param dpnId
88      *            DPN on which group flow to be installed
89      * @param groupdId
90      *            Uniquely identifiable Group Id for the group flow
91      * @param groupName
92      *            Name of the group flow
93      * @param buckets
94      *            List of the bucket actions for the group flow
95      */
96     public static void installGroup(IMdsalApiManager mdSalManager, BigInteger dpnId, long groupdId, String groupName,
97             List<BucketInfo> buckets) {
98         LOG.trace("Installing group flow on dpn {}", dpnId);
99         GroupEntity groupEntity = MDSALUtil.buildGroupEntity(dpnId, groupdId, groupName, GroupTypes.GroupAll, buckets);
100         mdSalManager.syncInstallGroup(groupEntity);
101     }
102
103     /**
104      * Get Default ARP Responder Drop flow on the DPN.
105      *
106      * @param dpnId
107      *            DPN on which group flow to be installed
108      */
109     public static FlowEntity getArpResponderTableMissFlow(BigInteger dpnId) {
110         return MDSALUtil.buildFlowEntity(dpnId, NwConstants.ARP_RESPONDER_TABLE,
111                 String.valueOf(NwConstants.ARP_RESPONDER_TABLE), NwConstants.TABLE_MISS_PRIORITY,
112                 ArpResponderConstant.DROP_FLOW_NAME.value(), 0, 0, NwConstants.COOKIE_ARP_RESPONDER,
113                 new ArrayList<MatchInfo>(),
114                 Collections.singletonList(new InstructionApplyActions(Collections.singletonList(new ActionDrop()))));
115     }
116
117     /**
118      * Get Bucket Actions for ARP Responder Group Flow.
119      *
120      * <p>
121      * Install Default Groups, Group has 1 Bucket
122      * </p>
123      * <ul>
124      * <li>Resubmit to Table {@link NwConstants#ARP_RESPONDER_TABLE}, for ARP
125      * Auto response from DPN itself</li>
126      * </ul>
127      *
128      * @param resubmitTableId
129      *            Resubmit Flow Table Id
130      * @return List of bucket actions
131      */
132     public static List<BucketInfo> getDefaultBucketInfos(short resubmitTableId) {
133         return Arrays.asList(
134                 new BucketInfo(Collections.singletonList(new ActionNxResubmit(resubmitTableId))));
135     }
136
137     /**
138      * Get Match Criteria for the ARP Responder Flow.
139      *
140      * <p>
141      * List of Match Criteria for ARP Responder
142      * </p>
143      * <ul>
144      * <li>Packet is ARP</li>
145      * <li>Packet is ARP Request</li>
146      * <li>The ARP packet is requesting for Gateway IP</li>
147      * <li>Metadata which is generated by using Service
148      * Index({@link NwConstants#L3VPN_SERVICE_INDEX}) Lport Tag
149      * ({@link MetaDataUtil#METADATA_MASK_LPORT_TAG}) and VRF
150      * ID({@link MetaDataUtil#METADATA_MASK_VRFID})</li>
151      * </ul>
152      *
153      * @param lportTag
154      *            LPort Tag
155      * @param elanInstance
156      *            Elan Instance
157      * @param ipAddress
158      *            Ip Address to be matched to this flow
159      * @return List of Match criteria
160      */
161     public static List<MatchInfo> getMatchCriteria(int lportTag, ElanInstance elanInstance,
162             String ipAddress) {
163
164         BigInteger metadata = ElanHelper.getElanMetadataLabel(elanInstance.getElanTag(), lportTag);
165         BigInteger metadataMask = ElanHelper.getElanMetadataMask();
166         return Arrays.asList(MatchEthernetType.ARP, MatchArpOp.REQUEST, new MatchArpTpa(ipAddress, "32"),
167                 new MatchMetadata(metadata, metadataMask));
168
169     }
170
171     /**
172      * Get List of actions for ARP Responder Flows.
173      *
174      * <p>
175      * Actions consists of all the ARP actions and Resubmit Action to table
176      * {@link NwConstants#ELAN_BASE_TABLE} such that packets can flow ELAN Rule
177      *
178      * @param ipAddress
179      *            IP Address for which ARP Response packet is to be generated
180      * @param macAddress
181      *            MacAddress for which ARP Response packet is to be generated
182      * @return List of ARP Responder Actions actions
183      */
184     public static List<Action> getActions(IInterfaceManager ifaceMgrRpcService, ItmRpcService itmRpcService,
185                                           String ifName, String ipAddress, String macAddress,
186                                           boolean isTunnelInterface) {
187
188         AtomicInteger actionCounter = new AtomicInteger();
189         List<Action> actions = arpActions.apply(actionCounter, macAddress, ipAddress);
190         actions.addAll(getEgressActionsForInterface(ifaceMgrRpcService, itmRpcService, ifName, actionCounter.get(),
191                 isTunnelInterface));
192         LOG.trace("Total Number of actions is {}", actionCounter);
193         return actions;
194
195     }
196
197     /**
198      * A Interface that represent lambda TriFunction.
199      *
200      * @param <T>
201      *            Input type
202      * @param <U>
203      *            Input type
204      * @param <S>
205      *            Input type
206      * @param <R>
207      *            Return Type
208      */
209     @SuppressWarnings("checkstyle:ParameterName")
210     public interface TriFunction<T, U, S, R> {
211         /**
212          * Apply the Action.
213          *
214          * @param t
215          *            Input1
216          * @param u
217          *            Input2
218          * @param s
219          *            Input3
220          * @return computed result
221          */
222         R apply(T t, U u, S s);
223     }
224
225     /**
226      * Lambda to apply arpAction. Inputs action counter, mac address and ip
227      * address
228      */
229     private static TriFunction<AtomicInteger, String, String, List<Action>> arpActions = (actionCounter, mac, ip) -> {
230         List<Action> actions = new ArrayList<>();
231         Collections.addAll(actions, new ActionMoveSourceDestinationEth().buildAction(actionCounter.getAndIncrement()),
232                 new ActionSetFieldEthernetSource(new MacAddress(mac)).buildAction(actionCounter.getAndIncrement()),
233                 new ActionSetArpOp(NwConstants.ARP_REPLY).buildAction(actionCounter.getAndIncrement()),
234                 new ActionMoveShaToTha().buildAction(actionCounter.getAndIncrement()),
235                 new ActionMoveSpaToTpa().buildAction(actionCounter.getAndIncrement()),
236                 new ActionLoadMacToSha(new MacAddress(mac)).buildAction(actionCounter.getAndIncrement()),
237                 new ActionLoadIpToSpa(ip).buildAction(actionCounter.getAndIncrement()),
238                 new ActionNxLoadInPort(BigInteger.ZERO).buildAction(actionCounter.getAndIncrement()));
239         return actions;
240
241     };
242
243     /**
244      * Get instruction list for ARP responder flows.
245      */
246     public static List<Instruction> getInterfaceInstructions(IInterfaceManager ifaceMgrRpcService, String interfaceName,
247             String ipAddress, String macAddress, ItmRpcService itmRpcService) {
248         List<Action> actions = ArpResponderUtil.getActions(ifaceMgrRpcService, itmRpcService, interfaceName, ipAddress,
249                 macAddress, false);
250         return Collections.singletonList(MDSALUtil.buildApplyActionsInstruction(actions));
251     }
252
253     /**
254      * Get instruction list for ARP responder flows originated from ext-net e.g.
255      * router-gw/fip.<br>
256      * The split-horizon bit should be reset in order to allow traffic from
257      * provider network to be routed back to flat/VLAN network and override the
258      * egress table drop flow.<br>
259      * In order to allow write-metadata in the ARP responder table the resubmit
260      * action needs to be replaced with goto instruction.
261      */
262     public static List<Instruction> getExtInterfaceInstructions(IInterfaceManager ifaceMgrRpcService,
263                                                                 ItmRpcService itmRpcService,
264                                                                 String extInterfaceName, String ipAddress,
265                                                                 String macAddress) {
266         AtomicInteger tableId = new AtomicInteger(-1);
267         List<Instruction> instructions = new ArrayList<>();
268         List<Action> actions = getActions(ifaceMgrRpcService, itmRpcService, extInterfaceName, ipAddress, macAddress,
269                 false);
270         actions.removeIf(v -> {
271             org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.Action actionClass = v
272                     .getAction();
273             if (actionClass instanceof NxActionResubmitRpcAddGroupCase) {
274                 tableId.set(((NxActionResubmitRpcAddGroupCase) actionClass).getNxResubmit().getTable());
275                 return true;
276             } else {
277                 return false;
278             }
279         });
280
281         instructions.add(MDSALUtil.buildApplyActionsInstruction(actions, 0));
282
283         if (tableId.get() != -1) {
284             // replace resubmit action with goto so it can co-exist with
285             // write-metadata
286             if ((short) tableId.get() > NwConstants.ARP_RESPONDER_TABLE) {
287                 instructions.add(new InstructionGotoTable((short) tableId.get()).buildInstruction(2));
288             } else {
289                 LOG.warn("Failed to insall responder flow for interface {}. Resubmit to {} can't be replaced with goto",
290                         extInterfaceName, tableId);
291             }
292         }
293
294         return instructions;
295     }
296
297     /**
298      * Install ARP Responder FLOW.
299      *
300      * @param mdSalManager
301      *            Reference of MDSAL API RPC that provides API for installing
302      *            flow
303      * @param dpnId
304      *            DPN on which flow to be installed
305      * @param flowId
306      *            Uniquely Identifiable Arp Responder Table flow Id
307      * @param flowName
308      *            Readable flow name
309      * @param priority
310      *            Flow Priority
311      * @param cookie
312      *            Flow Cookie
313      * @param matches
314      *            List of Match Criteria for the flow
315      * @param instructions
316      *            List of Instructions for the flow
317      */
318     public static void installFlow(IMdsalApiManager mdSalManager, BigInteger dpnId, String flowId, String flowName,
319             int priority, BigInteger cookie, List<MatchInfo> matches, List<Instruction> instructions) {
320         Flow flowEntity = MDSALUtil.buildFlowNew(NwConstants.ARP_RESPONDER_TABLE, flowId, priority, flowName, 0, 0,
321                 cookie, matches, instructions);
322         mdSalManager.installFlow(dpnId, flowEntity);
323     }
324
325     /**
326      * Remove flow form DPN.
327      *
328      * @param mdSalManager
329      *            Reference of MDSAL API RPC that provides API for installing
330      *            flow
331      * @param dpnId
332      *            DPN form which flow to be removed
333      * @param flowId
334      *            Uniquely Identifiable Arp Responder Table flow Id that is to
335      *            be removed
336      */
337     public static void removeFlow(IMdsalApiManager mdSalManager, BigInteger dpnId, String flowId) {
338         Flow flowEntity = MDSALUtil.buildFlow(NwConstants.ARP_RESPONDER_TABLE, flowId);
339         mdSalManager.removeFlow(dpnId, flowEntity);
340     }
341
342     /**
343      * Creates Uniquely Identifiable flow Id.
344      *
345      * @param lportTag
346      *            LportTag of the flow
347      * @param ipAdress
348      *            Gateway IP for which ARP Response flow to be installed
349      * @return Unique Flow Id
350      *
351      * @see ArpResponderConstant#FLOW_ID_FORMAT_WITH_LPORT
352      * @see ArpResponderConstant#FLOW_ID_FORMAT_WITHOUT_LPORT
353      */
354     public static String getFlowId(int lportTag, String ipAdress) {
355         return MessageFormat.format(ArpResponderConstant.FLOW_ID_FORMAT_WITH_LPORT.value(),
356                         NwConstants.ARP_RESPONDER_TABLE, lportTag, ipAdress);
357     }
358
359     /**
360      * Generate Cookie per flow.
361      *
362      * <p>
363      * Cookie is generated by Summation of
364      * {@link NwConstants#COOKIE_ARP_RESPONDER} + 1 + lportTag + Gateway IP
365      *
366      * @param lportTag
367      *            Lport Tag of the flow
368      * @param ipAddress
369      *            Gateway IP for which ARP Response flow to be installed
370      * @return Cookie
371      */
372     public static BigInteger generateCookie(int lportTag, String ipAddress) {
373         LOG.trace("IPAddress in long {}", ipAddress);
374         BigInteger cookie = NwConstants.COOKIE_ARP_RESPONDER.add(BigInteger.valueOf(255))
375                 .add(BigInteger.valueOf(ipTolong(ipAddress)));
376         return cookie.add(BigInteger.valueOf(lportTag));
377     }
378
379     private static BigInteger buildCookie(short tableId, int arpOpType) {
380         return NwConstants.COOKIE_ARP_RESPONDER.add(BigInteger.ONE).add(
381                 BigInteger.valueOf(tableId).add(BigInteger.valueOf(arpOpType)));
382     }
383
384     private static String buildFlowRef(short tableId, int arpOpType) {
385         return (tableId == NwConstants.ARP_CHECK_TABLE
386                 ? ArpResponderConstant.FLOWID_PREFIX_FOR_ARP_CHECK.value()
387                 : ArpResponderConstant.FLOWID_PREFIX_FOR_MY_GW_MAC.value()) + tableId + NwConstants.FLOWID_SEPARATOR
388                 + (arpOpType == NwConstants.ARP_REQUEST ? "arp.request" : "arp.replay");
389     }
390
391     public static FlowEntity createArpDefaultFlow(BigInteger dpId, short tableId, int arpOpType,
392             Supplier<List<MatchInfo>> matches, Supplier<List<ActionInfo>> actions) {
393
394         List<InstructionInfo> instructions = Collections.singletonList(new InstructionApplyActions(actions.get()));
395         return MDSALUtil.buildFlowEntity(dpId, tableId, buildFlowRef(tableId, arpOpType),
396                 NwConstants.DEFAULT_ARP_FLOW_PRIORITY, buildFlowRef(tableId, arpOpType), 0, 0,
397                 buildCookie(tableId, arpOpType), matches.get(), instructions);
398     }
399
400     /**
401      * Get IP Address in Long from String.
402      *
403      * @param address
404      *            IP Address that to be converted to long
405      * @return Long value of the IP Address
406      */
407     private static long ipTolong(String address) {
408
409         // Parse IP parts into an int array
410         long[] ip = new long[4];
411         String[] parts = address.split("\\.");
412
413         for (int i = 0; i < 4; i++) {
414             ip[i] = Long.parseLong(parts[i]);
415         }
416         // Add the above IP parts into an int number representing your IP
417         // in a 32-bit binary form
418         long ipNumbers = 0;
419         for (int i = 0; i < 4; i++) {
420             ipNumbers += ip[i] << (24 - (8 * i));
421         }
422         return ipNumbers;
423
424     }
425
426     /**
427      * Get List of Egress Action for the VPN interface.
428      *
429      * @param ifaceMgrRpcService
430      *            Interface Manager RPC reference that invokes API to retrieve
431      *            Egress Action
432      * @param ifName
433      *            VPN Interface for which Egress Action to be retrieved
434      * @param actionCounter
435      *            Action Key
436      * @return List of Egress Actions
437      */
438     public static List<Action> getEgressActionsForInterface(IInterfaceManager ifaceMgrRpcService,
439                                                             ItmRpcService itmRpcService, String ifName,
440                                                             int actionCounter, boolean isTunnelInterface) {
441         if (isTunnelInterface && ifaceMgrRpcService.isItmDirectTunnelsEnabled()) {
442             try {
443                 RpcResult result = itmRpcService.getEgressActionsForTunnel(new GetEgressActionsForTunnelInputBuilder()
444                         .setIntfName(ifName).build()).get();
445                 List<Action> listActions = new ArrayList<>();
446                 if (!result.isSuccessful()) {
447                     LOG.error("getEgressActionsForInterface: RPC Call to Get egress actions for interface {} "
448                             + "returned with Errors {}", ifName, result.getErrors());
449                 } else {
450                     listActions = ((GetEgressActionsForTunnelOutput) result.getResult()).getAction();
451                 }
452                 return listActions;
453             } catch (InterruptedException | ExecutionException e) {
454                 LOG.error("getEgressActionsForInterface: Exception when egress actions for interface {}", ifName, e);
455             }
456         } else {
457             List<ActionInfo> actionInfos = ifaceMgrRpcService.getInterfaceEgressActions(ifName);
458             AtomicInteger counter = new AtomicInteger(actionCounter);
459             return actionInfos.stream().map(v -> v.buildAction(counter.getAndIncrement())).collect(Collectors.toList());
460         }
461         return Collections.emptyList();
462     }
463
464     /**
465      * Uses the IdManager to retrieve ARP Responder GroupId from ELAN pool.
466      *
467      * @param idManager
468      *            the id manager
469      * @return the integer
470      */
471     public static Long retrieveStandardArpResponderGroupId(IdManagerService idManager) {
472
473         AllocateIdInput getIdInput = new AllocateIdInputBuilder()
474                 .setPoolName(ArpResponderConstant.ELAN_ID_POOL_NAME.value())
475                 .setIdKey(ArpResponderConstant.ARP_RESPONDER_GROUP_ID.value()).build();
476
477         try {
478             Future<RpcResult<AllocateIdOutput>> result = idManager.allocateId(getIdInput);
479             RpcResult<AllocateIdOutput> rpcResult = result.get();
480             if (rpcResult.isSuccessful()) {
481                 LOG.trace("Retrieved Group Id is {}", rpcResult.getResult().getIdValue());
482                 return rpcResult.getResult().getIdValue();
483             } else {
484                 LOG.warn("RPC Call to Allocate Id returned with Errors {}", rpcResult.getErrors());
485             }
486         } catch (InterruptedException | ExecutionException e) {
487             LOG.warn("Exception when Allocating Id", e);
488         }
489         return 0L;
490     }
491
492 }