6c23f5b767747e653776fe16a58a7eaa00cc8cc8
[netvirt.git] / vpnservice / vpnmanager / vpnmanager-impl / src / main / java / org / opendaylight / netvirt / vpnmanager / arp / responder / ArpResponderUtil.java
1 /*
2  * Copyright (c) 2016 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.vpnmanager.arp.responder;
9
10 import java.math.BigInteger;
11 import java.text.MessageFormat;
12 import java.util.ArrayList;
13 import java.util.Collections;
14 import java.util.Iterator;
15 import java.util.List;
16 import java.util.concurrent.ExecutionException;
17 import java.util.concurrent.Future;
18
19 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
20 import org.opendaylight.genius.mdsalutil.BucketInfo;
21 import org.opendaylight.genius.mdsalutil.FlowEntity;
22 import org.opendaylight.genius.mdsalutil.GroupEntity;
23 import org.opendaylight.genius.mdsalutil.MDSALUtil;
24 import org.opendaylight.genius.mdsalutil.MatchFieldType;
25 import org.opendaylight.genius.mdsalutil.MatchInfo;
26 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
27 import org.opendaylight.genius.mdsalutil.NwConstants;
28 import org.opendaylight.genius.mdsalutil.actions.ActionDrop;
29 import org.opendaylight.genius.mdsalutil.actions.ActionLoadIpToSpa;
30 import org.opendaylight.genius.mdsalutil.actions.ActionLoadMacToSha;
31 import org.opendaylight.genius.mdsalutil.actions.ActionMoveShaToTha;
32 import org.opendaylight.genius.mdsalutil.actions.ActionMoveSourceDestinationEth;
33 import org.opendaylight.genius.mdsalutil.actions.ActionMoveSpaToTpa;
34 import org.opendaylight.genius.mdsalutil.actions.ActionNxLoadInPort;
35 import org.opendaylight.genius.mdsalutil.actions.ActionNxResubmit;
36 import org.opendaylight.genius.mdsalutil.actions.ActionPuntToController;
37 import org.opendaylight.genius.mdsalutil.actions.ActionSetArpOp;
38 import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldEthernetSource;
39 import org.opendaylight.genius.mdsalutil.instructions.InstructionApplyActions;
40 import org.opendaylight.genius.mdsalutil.instructions.InstructionGotoTable;
41 import org.opendaylight.genius.mdsalutil.instructions.InstructionWriteMetadata;
42 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
43 import org.opendaylight.netvirt.vpnmanager.ArpReplyOrRequest;
44 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionKey;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInput;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInputBuilder;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdOutput;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
53 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.GetEgressActionsForInterfaceInputBuilder;
54 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.GetEgressActionsForInterfaceOutput;
55 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.OdlInterfaceRpcService;
56 import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.GroupTypes;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.openflowplugin.extension.nicira.action.rev140714.add.group.input.buckets.bucket.action.action.NxActionResubmitRpcAddGroupCase;
58 import org.opendaylight.yangtools.yang.common.RpcResult;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 /**
63  * Arp Responder Utility Class
64  *
65  *
66  */
67 public class ArpResponderUtil {
68
69     private final static Logger LOG = LoggerFactory
70             .getLogger(ArpResponderUtil.class);
71
72     private static final long WAIT_TIME_FOR_SYNC_INSTALL = Long.getLong("wait.time.sync.install", 300L);
73
74     /**
75      * A Utility class
76      */
77     private ArpResponderUtil() {
78
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(final IMdsalApiManager mdSalManager,
97             final BigInteger dpnId, final long groupdId, final String groupName,
98             final List<BucketInfo> buckets) {
99         LOG.trace("Installing group flow on dpn {}", dpnId);
100         final GroupEntity groupEntity = MDSALUtil.buildGroupEntity(dpnId,
101                 groupdId, groupName, GroupTypes.GroupAll, buckets);
102         mdSalManager.syncInstallGroup(groupEntity, WAIT_TIME_FOR_SYNC_INSTALL);
103         try {
104             Thread.sleep(WAIT_TIME_FOR_SYNC_INSTALL);
105         } catch (InterruptedException e1) {
106             LOG.warn("Error while waiting for ARP Responder Group Entry to be installed on DPN {} ", dpnId);
107         }
108     }
109
110     /**
111      * Get Default ARP Responder Drop flow on the DPN
112      *
113      * @param dpnId
114      *            DPN on which group flow to be installed
115      *
116      */
117     public static FlowEntity getArpResponderTableMissFlow(final BigInteger dpnId) {
118         return MDSALUtil.buildFlowEntity(dpnId, NwConstants.ARP_RESPONDER_TABLE,
119             String.valueOf(NwConstants.ARP_RESPONDER_TABLE),
120             NwConstants.TABLE_MISS_PRIORITY,
121             ArpResponderConstant.DROP_FLOW_NAME.value(), 0, 0,
122             NwConstants.COOKIE_ARP_RESPONDER,
123             new ArrayList<MatchInfo>(),
124             Collections.singletonList(new InstructionApplyActions(Collections.singletonList(new ActionDrop()))));
125     }
126
127     /**
128      * Get Bucket Actions for ARP Responder Group Flow
129      *
130      * <p>
131      * Install Default Groups, Group has 3 Buckets
132      * </p>
133      * <ul>
134      * <li>Punt to controller</li>
135      * <li>Resubmit to Table {@link NwConstants#LPORT_DISPATCHER_TABLE}, for
136      * ELAN flooding
137      * <li>Resubmit to Table {@link NwConstants#ARP_RESPONDER_TABLE}, for ARP
138      * Auto response from DPN itself</li>
139      * </ul>
140      *
141      * @param resubmitTableId
142      *            Resubmit Flow Table Id
143      * @param resubmitTableId2
144      *            Resubmit Flow Table Id
145      * @return List of bucket actions
146      */
147     public static List<BucketInfo> getDefaultBucketInfos(
148             final short resubmitTableId, final short resubmitTableId2) {
149         final List<BucketInfo> buckets = new ArrayList<>();
150         buckets.add(new BucketInfo(Collections.singletonList(new ActionPuntToController())));
151         buckets.add(new BucketInfo(Collections.singletonList(new ActionNxResubmit(resubmitTableId))));
152         buckets.add(new BucketInfo(Collections.singletonList(new ActionNxResubmit(resubmitTableId2))));
153         return buckets;
154     }
155
156     /**
157      * Get Match Criteria for the ARP Responder Flow
158      * <p>
159      * List of Match Criteria for ARP Responder
160      * </p>
161      * <ul>
162      * <li>Packet is ARP</li>
163      * <li>Packet is ARP Request</li>
164      * <li>The ARP packet is requesting for Gateway IP</li>
165      * <li>Metadata which is generated by using Service
166      * Index({@link NwConstants#L3VPN_SERVICE_INDEX}) Lport Tag
167      * ({@link MetaDataUtil#METADATA_MASK_LPORT_TAG}) and VRF
168      * ID({@link MetaDataUtil#METADATA_MASK_VRFID})</li>
169      * </ul>
170      *
171      * @param lPortTag
172      *            LPort Tag
173      * @param vpnId
174      *            VPN ID
175      * @param ipAddress
176      *            Gateway IP
177      * @return List of Match criteria
178      */
179     public static List<MatchInfo> getMatchCriteria(final int lPortTag,
180             final long vpnId, final String ipAddress) {
181
182         final List<MatchInfo> matches = new ArrayList<MatchInfo>();
183         short mIndex = NwConstants.L3VPN_SERVICE_INDEX;
184         final BigInteger metadata = MetaDataUtil.getMetaDataForLPortDispatcher(
185                 lPortTag, ++mIndex, MetaDataUtil.getVpnIdMetadata(vpnId));
186         final BigInteger metadataMask = MetaDataUtil
187                 .getMetaDataMaskForLPortDispatcher(
188                         MetaDataUtil.METADATA_MASK_SERVICE_INDEX,
189                         MetaDataUtil.METADATA_MASK_LPORT_TAG,
190                         MetaDataUtil.METADATA_MASK_VRFID);
191
192         // Matching Arp request flows
193         matches.add(new MatchInfo(MatchFieldType.eth_type,
194                 new long[] { NwConstants.ETHTYPE_ARP }));
195         matches.add(new MatchInfo(MatchFieldType.metadata,
196                 new BigInteger[] { metadata, metadataMask }));
197         matches.add(new MatchInfo(MatchFieldType.arp_op,
198                 new long[] { ArpReplyOrRequest.REQUEST.getArpOperation() }));
199         matches.add(new MatchInfo(MatchFieldType.arp_tpa,
200                 new String[] { ipAddress, "32" }));
201         return matches;
202
203     }
204
205     /**
206      * Get List of actions for ARP Responder Flows
207      *
208      * Actions consists of all the ARP actions from
209      * and Egress Actions Retrieved
210      *
211      * @param ifaceMgrRpcService
212      *            Interface manager RPC reference to invoke RPC to get Egress
213      *            actions for the interface
214      * @param vpnInterface
215      *            VPN Interface for which flow to be installed
216      * @param ipAddress
217      *            Gateway IP Address
218      * @param macAddress
219      *            Gateway MacAddress
220      * @return List of ARP Responder Actions actions
221      */
222     public static List<Action> getActions(
223             final OdlInterfaceRpcService ifaceMgrRpcService,
224             final String vpnInterface, final String ipAddress,
225             final String macAddress) {
226
227         final List<Action> actions = new ArrayList<>();
228         int actionCounter = 0;
229         actions.add(new ActionMoveSourceDestinationEth().buildAction(actionCounter++));
230         actions.add(new ActionSetFieldEthernetSource(new MacAddress(macAddress)).buildAction(actionCounter++));
231         actions.add(new ActionSetArpOp(NwConstants.ARP_REPLY).buildAction(actionCounter++));
232         actions.add(new ActionMoveShaToTha().buildAction(actionCounter++));
233         actions.add(new ActionMoveSpaToTpa().buildAction(actionCounter++));
234         actions.add(new ActionLoadMacToSha(new MacAddress(macAddress)).buildAction(actionCounter++));
235         actions.add(new ActionLoadIpToSpa(ipAddress).buildAction(actionCounter++));
236         //A temporary fix until to send packet to incoming port by loading IN_PORT with zero, until in_port is overridden in table=0
237         actions.add(new ActionNxLoadInPort(BigInteger.ZERO).buildAction(actionCounter++));
238
239         actions.addAll(getEgressActionsForInterface(ifaceMgrRpcService,
240                 vpnInterface, actionCounter));
241         LOG.trace("Total Number of actions is {}", actionCounter);
242         return actions;
243
244     }
245
246     /**
247      * Get instruction list for ARP responder flows originated from ext-net e.g.
248      * router-gw/fip.<br>
249      * The split-horizon bit should be reset in order to allow traffic from
250      * provider network to be routed back to flat/VLAN network and override the
251      * egress table drop flow.<br>
252      * In order to allow write-metadata in the ARP responder table the resubmit
253      * action needs to be replaced with goto instruction.
254      *
255      * @param ifaceMgrRpcService
256      * @param extInterfaceName
257      * @param ipAddress
258      * @param macAddress
259      * @return
260      */
261     public static List<Instruction> getExtInterfaceInstructions(final OdlInterfaceRpcService ifaceMgrRpcService,
262             final String extInterfaceName, final String ipAddress, final String macAddress) {
263         Short tableId = null;
264         List<Instruction> instructions = new ArrayList<>();
265         List<Action> actions = getActions(ifaceMgrRpcService, extInterfaceName, ipAddress, macAddress);
266         for (Iterator<Action> iterator = actions.iterator(); iterator.hasNext();) {
267             org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.Action actionClass = iterator
268                     .next().getAction();
269             if (actionClass instanceof NxActionResubmitRpcAddGroupCase) {
270                 tableId = ((NxActionResubmitRpcAddGroupCase) actionClass).getNxResubmit().getTable();
271                 iterator.remove();
272                 break;
273             }
274         }
275
276         instructions.add(MDSALUtil.buildApplyActionsInstruction(actions, 0));
277         // reset the split-horizon bit to allow traffic to be sent back to the
278         // provider port
279         instructions.add(
280                 new InstructionWriteMetadata(BigInteger.ZERO, MetaDataUtil.METADATA_MASK_SH_FLAG).buildInstruction(1));
281
282         if (tableId != null) {
283             // replace resubmit action with goto so it can co-exist with
284             // write-metadata
285             if (tableId > NwConstants.ARP_RESPONDER_TABLE) {
286                 instructions.add(new InstructionGotoTable(tableId).buildInstruction(2));
287             } else {
288                 LOG.warn("Failed to insall responder flow for interface {}. Resubmit to {} can't be replaced with goto",
289                         extInterfaceName, tableId);
290             }
291         }
292
293         return instructions;
294     }
295
296     /**
297      * Install ARP Responder FLOW
298      *
299      * @param mdSalManager
300      *            Reference of MDSAL API RPC that provides API for installing
301      *            flow
302      * @param writeInvTxn
303      *            Write Transaction to write the flow
304      * @param dpnId
305      *            DPN on which flow to be installed
306      * @param flowId
307      *            Uniquely Identifiable Arp Responder Table flow Id
308      * @param flowName
309      *            Readable flow name
310      * @param priority
311      *            Flow Priority
312      * @param cookie
313      *            Flow Cookie
314      * @param matches
315      *            List of Match Criteria for the flow
316      * @param instructions
317      *            List of Instructions for the flow
318      */
319     public static void installFlow(final IMdsalApiManager mdSalManager,
320             final WriteTransaction writeInvTxn, final BigInteger dpnId,
321             final String flowId, final String flowName,
322             final int priority, final BigInteger cookie,
323             List<MatchInfo> matches, List<Instruction> instructions) {
324
325         final Flow flowEntity = MDSALUtil.buildFlowNew(
326                 NwConstants.ARP_RESPONDER_TABLE, flowId, priority, flowName, 0,
327                 0, cookie, matches, instructions);
328         mdSalManager.addFlowToTx(dpnId, flowEntity, writeInvTxn);
329     }
330
331     /**
332      * Remove flow form DPN
333      *
334      * @param mdSalManager
335      *            Reference of MDSAL API RPC that provides API for installing
336      *            flow
337      * @param writeInvTxn
338      *            Write Transaction to write the flow
339      * @param dpnId
340      *            DPN form which flow to be removed
341      * @param flowId
342      *            Uniquely Identifiable Arp Responder Table flow Id that is to
343      *            be removed
344      */
345     public static void removeFlow(final IMdsalApiManager mdSalManager,
346             final WriteTransaction writeInvTxn,
347             final BigInteger dpnId, final String flowId) {
348         final Flow flowEntity = MDSALUtil
349                 .buildFlow(NwConstants.ARP_RESPONDER_TABLE, flowId);
350         mdSalManager.removeFlowToTx(dpnId, flowEntity, writeInvTxn);
351     }
352
353     /**
354      * Creates Uniquely Identifiable flow Id
355      * <p>
356      * <b>Refer:</b> {@link ArpResponderConstant#FLOW_ID_FORMAT}
357      *
358      * @param lportTag
359      *            LportTag of the flow
360      * @param gwIp
361      *            Gateway IP for which ARP Response flow to be installed
362      * @return Unique Flow Id
363      */
364     public static String getFlowID(final int lportTag, final String gwIp) {
365         return MessageFormat.format(ArpResponderConstant.FLOW_ID_FORMAT.value(),
366                 NwConstants.ARP_RESPONDER_TABLE, lportTag, gwIp);
367     }
368
369     /**
370      * Generate Cookie per flow
371      * <p>
372      * Cookie is generated by Summation of
373      * {@link NwConstants#COOKIE_ARP_RESPONDER} + 1 + lportTag + Gateway IP
374      *
375      * @param lportTag
376      *            Lport Tag of the flow
377      * @param gwIp
378      *            Gateway IP for which ARP Response flow to be installed
379      * @return Cookie
380      */
381     public static BigInteger generateCookie(final long lportTag,
382             final String gwIp) {
383         LOG.trace("IPAddress in long {}", gwIp);
384         return NwConstants.COOKIE_ARP_RESPONDER.add(BigInteger.ONE)
385                 .add(BigInteger.valueOf(lportTag))
386                 .add(BigInteger.valueOf(ipTolong(gwIp)));
387     }
388
389     /**
390      * Get IP Address in Long from String
391      *
392      * @param address
393      *            IP Address that to be converted to long
394      * @return Long value of the IP Address
395      */
396     private static long ipTolong(String address) {
397
398         // Parse IP parts into an int array
399         long[] ip = new long[4];
400         String[] parts = address.split("\\.");
401
402         for (int i = 0; i < 4; i++) {
403             ip[i] = Long.parseLong(parts[i]);
404         }
405         // Add the above IP parts into an int number representing your IP
406         // in a 32-bit binary form
407         long ipNumbers = 0;
408         for (int i = 0; i < 4; i++) {
409             ipNumbers += ip[i] << (24 - (8 * i));
410         }
411         return ipNumbers;
412
413     }
414
415     /**
416      * Get List of Egress Action for the VPN interface
417      *
418      * @param ifaceMgrRpcService
419      *            Interface Manager RPC reference that invokes API to retrieve
420      *            Egress Action
421      * @param ifName
422      *            VPN Interface for which Egress Action to be retrieved
423      * @param actionCounter
424      *            Action Key
425      * @return List of Egress Actions
426      */
427     public static List<Action> getEgressActionsForInterface(
428             final OdlInterfaceRpcService ifaceMgrRpcService, String ifName,
429             int actionCounter) {
430         final List<Action> listActions = new ArrayList<>();
431         try {
432             final RpcResult<GetEgressActionsForInterfaceOutput> result = ifaceMgrRpcService
433                     .getEgressActionsForInterface(
434                             new GetEgressActionsForInterfaceInputBuilder()
435                                     .setIntfName(ifName).build())
436                     .get();
437             if (result.isSuccessful()) {
438                 final List<org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.Action> actions = result
439                         .getResult().getAction();
440                 for (final Action action : actions) {
441
442                     listActions
443                             .add(new org.opendaylight.yang.gen.v1.urn.opendaylight.action.types.rev131112.action.list.ActionBuilder(
444                                     action).setKey(new ActionKey(actionCounter))
445                                             .setOrder(actionCounter++).build());
446
447                 }
448             } else {
449                 LOG.warn(
450                         "RPC Call to Get egress actions for interface {} returned with Errors {}",
451                         ifName, result.getErrors());
452             }
453         } catch (InterruptedException | ExecutionException e) {
454             LOG.warn("Exception when egress actions for interface {}", ifName,
455                     e);
456         }
457         return listActions;
458     }
459
460     /**
461      * Uses the IdManager to retrieve ARP Responder GroupId from ELAN pool.
462      *
463      * @param idManager
464      *            the id manager
465      * @return the integer
466      */
467     public static Long retrieveStandardArpResponderGroupId(IdManagerService idManager) {
468
469         AllocateIdInput getIdInput = new AllocateIdInputBuilder().setPoolName(ArpResponderConstant.ELAN_ID_POOL_NAME.value())
470                 .setIdKey(ArpResponderConstant.ARP_RESPONDER_GROUP_ID.value()).build();
471
472         try {
473             Future<RpcResult<AllocateIdOutput>> result = idManager.allocateId(getIdInput);
474             RpcResult<AllocateIdOutput> rpcResult = result.get();
475             if (rpcResult.isSuccessful()) {
476                 LOG.trace("Retrieved Group Id is {}", rpcResult.getResult().getIdValue().longValue());
477                 return rpcResult.getResult().getIdValue().longValue();
478             } else {
479                 LOG.warn("RPC Call to Allocate Id returned with Errors {}", rpcResult.getErrors());
480             }
481         } catch (InterruptedException | ExecutionException e) {
482             LOG.warn("Exception when Allocating Id", e);
483         }
484         return 0L;
485     }
486
487 }