NETVIRT-1063 SNAT flows fails to install sporadically(conntrack SNAT)
[netvirt.git] / vpnservice / natservice / natservice-impl / src / main / java / org / opendaylight / netvirt / natservice / internal / AbstractSnatService.java
1 /*
2  * Copyright (c) 2017 Red Hat, Inc. 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.natservice.internal;
9
10 import java.math.BigInteger;
11 import java.util.ArrayList;
12 import java.util.List;
13 import java.util.concurrent.ExecutionException;
14 import java.util.concurrent.Future;
15 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
16 import org.opendaylight.genius.mdsalutil.ActionInfo;
17 import org.opendaylight.genius.mdsalutil.BucketInfo;
18 import org.opendaylight.genius.mdsalutil.FlowEntity;
19 import org.opendaylight.genius.mdsalutil.GroupEntity;
20 import org.opendaylight.genius.mdsalutil.InstructionInfo;
21 import org.opendaylight.genius.mdsalutil.MDSALUtil;
22 import org.opendaylight.genius.mdsalutil.MatchInfo;
23 import org.opendaylight.genius.mdsalutil.MatchInfoBase;
24 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
25 import org.opendaylight.genius.mdsalutil.NwConstants;
26 import org.opendaylight.genius.mdsalutil.actions.ActionGroup;
27 import org.opendaylight.genius.mdsalutil.actions.ActionNxLoadMetadata;
28 import org.opendaylight.genius.mdsalutil.actions.ActionNxResubmit;
29 import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldTunnelId;
30 import org.opendaylight.genius.mdsalutil.instructions.InstructionApplyActions;
31 import org.opendaylight.genius.mdsalutil.instructions.InstructionGotoTable;
32 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
33 import org.opendaylight.genius.mdsalutil.matches.MatchEthernetType;
34 import org.opendaylight.genius.mdsalutil.matches.MatchIpv4Destination;
35 import org.opendaylight.genius.mdsalutil.matches.MatchMetadata;
36 import org.opendaylight.genius.mdsalutil.matches.MatchTunnelId;
37 import org.opendaylight.netvirt.natservice.api.SnatServiceListener;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInput;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdInputBuilder;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.AllocateIdOutput;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.TunnelTypeBase;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.TunnelTypeGre;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.TunnelTypeVxlan;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.OdlInterfaceRpcService;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.GetTunnelInterfaceNameInputBuilder;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.GetTunnelInterfaceNameOutput;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.itm.rpcs.rev160406.ItmRpcService;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.GroupTypes;
51 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.ext.routers.Routers;
52 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.ext.routers.routers.ExternalIps;
53 import org.opendaylight.yangtools.yang.common.RpcResult;
54 import org.slf4j.Logger;
55 import org.slf4j.LoggerFactory;
56
57 public abstract class AbstractSnatService implements SnatServiceListener {
58     private static final Logger LOG = LoggerFactory.getLogger(AbstractSnatService.class);
59
60     static final int LOAD_START = mostSignificantBit(MetaDataUtil.METADATA_MASK_SH_FLAG.intValue());
61     static final int LOAD_END = mostSignificantBit(MetaDataUtil.METADATA_MASK_VRFID.intValue() | MetaDataUtil
62             .METADATA_MASK_SH_FLAG.intValue());
63
64     protected final DataBroker dataBroker;
65     protected final IMdsalApiManager mdsalManager;
66     protected final IdManagerService idManager;
67     protected final NAPTSwitchSelector naptSwitchSelector;
68     protected final ItmRpcService itmManager;
69     protected final OdlInterfaceRpcService interfaceManager;
70
71     protected AbstractSnatService(final DataBroker dataBroker, final IMdsalApiManager mdsalManager,
72             final ItmRpcService itmManager,
73             final OdlInterfaceRpcService interfaceManager,
74             final IdManagerService idManager,
75             final NAPTSwitchSelector naptSwitchSelector) {
76         this.dataBroker = dataBroker;
77         this.mdsalManager = mdsalManager;
78         this.itmManager = itmManager;
79         this.interfaceManager = interfaceManager;
80         this.idManager = idManager;
81         this.naptSwitchSelector = naptSwitchSelector;
82     }
83
84     protected DataBroker getDataBroker() {
85         return dataBroker;
86     }
87
88     public void init() {
89         LOG.info("{} init", getClass().getSimpleName());
90     }
91
92     public void close() {
93         LOG.debug("AbstractSnatService Closed");
94     }
95
96     @Override
97     public boolean handleSnatAllSwitch(Routers routers, BigInteger primarySwitchId,  int addOrRemove) {
98         LOG.debug("handleSnatAllSwitch : Handle Snat in all switches");
99         String routerName = routers.getRouterName();
100         List<BigInteger> switches = naptSwitchSelector.getDpnsForVpn(routerName);
101         /*
102          * Primary switch handled separately since the pseudo port created may
103          * not be present in the switch list on delete.
104          */
105         handleSnat(routers, primarySwitchId, primarySwitchId, addOrRemove);
106         for (BigInteger dpnId : switches) {
107             if (primarySwitchId != dpnId) {
108                 handleSnat(routers, primarySwitchId, dpnId, addOrRemove);
109             }
110         }
111
112         return true;
113     }
114
115     @Override
116     public boolean handleSnat(Routers routers, BigInteger primarySwitchId, BigInteger dpnId,  int addOrRemove) {
117
118         // Handle non NAPT switches and NAPT switches separately
119         if (!dpnId.equals(primarySwitchId)) {
120             LOG.debug("handleSnat : Handle non NAPT switch {}", dpnId);
121             installSnatCommonEntriesForNonNaptSwitch(routers, primarySwitchId, dpnId, addOrRemove);
122             installSnatSpecificEntriesForNonNaptSwitch(routers, dpnId, addOrRemove);
123         } else {
124             LOG.debug("handleSnat : Handle NAPT switch {}", dpnId);
125             installSnatCommonEntriesForNaptSwitch(routers, dpnId, addOrRemove);
126             installSnatSpecificEntriesForNaptSwitch(routers, dpnId, addOrRemove);
127
128         }
129         return true;
130     }
131
132     protected void installSnatCommonEntriesForNaptSwitch(Routers routers, BigInteger dpnId,  int addOrRemove) {
133         String routerName = routers.getRouterName();
134         Long routerId = NatUtil.getVpnId(dataBroker, routerName);
135         installDefaultFibRouteForSNAT(dpnId, routerId, addOrRemove);
136         List<ExternalIps> externalIps = routers.getExternalIps();
137         if (externalIps.isEmpty()) {
138             LOG.error("AbstractSnatService: installSnatCommonEntriesForNaptSwitch no externalIP present"
139                     + " for routerId {}",
140                     routerId);
141             return;
142         }
143         //The logic now handle only one external IP per router, others if present will be ignored.
144         String externalIp = externalIps.get(0).getIpAddress();
145         Uuid externalSubnetId = externalIps.get(0).getSubnetId();
146         long extSubnetId = NatConstants.INVALID_ID;
147         if (addOrRemove == NwConstants.ADD_FLOW) {
148             extSubnetId = NatUtil.getExternalSubnetVpnId(dataBroker,externalSubnetId);
149         }
150         installInboundFibEntry(dpnId, externalIp, routerName, routerId, extSubnetId, addOrRemove);
151         installInboundTerminatingServiceTblEntry(dpnId, routerId, routerName, externalIp, extSubnetId, addOrRemove);
152     }
153
154     protected void installSnatCommonEntriesForNonNaptSwitch(Routers routers, BigInteger primarySwitchId,
155             BigInteger dpnId, int addOrRemove) {
156         String routerName = routers.getRouterName();
157         Long routerId = NatUtil.getVpnId(dataBroker, routerName);
158         List<ExternalIps> externalIps = routers.getExternalIps();
159         if (externalIps.isEmpty()) {
160             LOG.error("AbstractSnatService: installSnatCommonEntriesForNaptSwitch no externalIP present"
161                     + " for routerId {}",
162                     routerId);
163             return;
164         }
165         String externalIp = externalIps.get(0).getIpAddress();
166         Uuid externalSubnetId = externalIps.get(0).getSubnetId();
167         long extSubnetId = NatConstants.INVALID_ID;
168         if (addOrRemove == NwConstants.ADD_FLOW) {
169             extSubnetId = NatUtil.getExternalSubnetVpnId(dataBroker,externalSubnetId);
170         }
171
172         installDefaultFibRouteForSNAT(dpnId, routerId, addOrRemove);
173         installSnatMissEntry(dpnId, routerId, routerName, primarySwitchId, externalIp, extSubnetId, addOrRemove);
174     }
175
176     protected abstract void installSnatSpecificEntriesForNaptSwitch(Routers routers, BigInteger dpnId,
177             int addOrRemove);
178
179     protected abstract void installSnatSpecificEntriesForNonNaptSwitch(Routers routers, BigInteger dpnId,
180             int addOrRemove);
181
182     protected void installInboundFibEntry(BigInteger dpnId, String externalIp, String routerName, Long routerId,
183             long extSubnetId, int addOrRemove) {
184         List<MatchInfo> matches = new ArrayList<>();
185         matches.add(MatchEthernetType.IPV4);
186         if (addOrRemove == NwConstants.ADD_FLOW) {
187             if (extSubnetId == NatConstants.INVALID_ID) {
188                 LOG.error("ConntrackBasedSnatService : installInboundFibEntry : external subnet id is invalid.");
189                 return;
190             }
191             matches.add(new MatchMetadata(MetaDataUtil.getVpnIdMetadata(extSubnetId),
192                     MetaDataUtil.METADATA_MASK_VRFID));
193         }
194         matches.add(new MatchIpv4Destination(externalIp, "32"));
195
196         ArrayList<ActionInfo> listActionInfo = new ArrayList<>();
197         ArrayList<InstructionInfo> instructionInfo = new ArrayList<>();
198         listActionInfo.add(new ActionNxResubmit(NwConstants.INBOUND_NAPT_TABLE));
199         instructionInfo.add(new InstructionApplyActions(listActionInfo));
200
201         String flowRef = getFlowRef(dpnId, NwConstants.L3_FIB_TABLE, routerId);
202         flowRef = flowRef + "inbound" + externalIp;
203         syncFlow(dpnId, NwConstants.L3_FIB_TABLE, flowRef, NatConstants.SNAT_FIB_FLOW_PRIORITY, flowRef,
204                 NwConstants.COOKIE_SNAT_TABLE, matches, instructionInfo, addOrRemove);
205     }
206
207     protected void installSnatMissEntry(BigInteger dpnId, Long routerId, String routerName, BigInteger primarySwitchId,
208             String externalIp, long extSubnetId, int addOrRemove) {
209         LOG.debug("installSnatMissEntry : Installing SNAT miss entry in switch {}", dpnId);
210         List<ActionInfo> listActionInfoPrimary = new ArrayList<>();
211         String ifNamePrimary = getTunnelInterfaceName(dpnId, primarySwitchId);
212         List<BucketInfo> listBucketInfo = new ArrayList<>();
213         if (ifNamePrimary != null) {
214             LOG.debug("installSnatMissEntry : On Non- Napt switch , Primary Tunnel interface is {}", ifNamePrimary);
215             listActionInfoPrimary = NatUtil.getEgressActionsForInterface(interfaceManager, ifNamePrimary, routerId);
216         }
217         BucketInfo bucketPrimary = new BucketInfo(listActionInfoPrimary);
218         listBucketInfo.add(0, bucketPrimary);
219         LOG.debug("installSnatMissEntry : installSnatMissEntry called for dpnId {} with primaryBucket {} ", dpnId,
220                 listBucketInfo.get(0));
221         // Install the select group
222         long groupId = createGroupId(getGroupIdKey(routerName));
223         GroupEntity groupEntity = MDSALUtil.buildGroupEntity(dpnId, groupId, routerName, GroupTypes.GroupAll,
224                 listBucketInfo);
225         LOG.debug("installSnatMissEntry : installing the SNAT to NAPT GroupEntity:{}", groupEntity);
226         mdsalManager.installGroup(groupEntity);
227         // Install miss entry pointing to group
228         LOG.debug("installSnatMissEntry : buildSnatFlowEntity is called for dpId {}, routerName {} and groupId {}",
229                 dpnId, routerName, groupId);
230         List<MatchInfo> matches = new ArrayList<>();
231         matches.add(new MatchEthernetType(0x0800L));
232         matches.add(new MatchMetadata(MetaDataUtil.getVpnIdMetadata(routerId), MetaDataUtil.METADATA_MASK_VRFID));
233
234
235         List<ActionInfo> actionsInfo = new ArrayList<>();
236         actionsInfo.add(new ActionSetFieldTunnelId(BigInteger.valueOf(routerId)));
237         LOG.debug("installSnatMissEntry : Setting the tunnel to the list of action infos {}", actionsInfo);
238         actionsInfo.add(new ActionGroup(groupId));
239         List<InstructionInfo> instructions = new ArrayList<>();
240         instructions.add(new InstructionApplyActions(actionsInfo));
241         String flowRef = getFlowRef(dpnId, NwConstants.PSNAT_TABLE, routerId);
242         syncFlow(dpnId, NwConstants.PSNAT_TABLE, flowRef,  NatConstants.DEFAULT_PSNAT_FLOW_PRIORITY, flowRef,
243                 NwConstants.COOKIE_SNAT_TABLE, matches, instructions, addOrRemove);
244         //Install the FIB entry for traffic destined to SNAT IP in Non-NAPT switch.
245         matches = new ArrayList<>();
246         actionsInfo = new ArrayList<>();
247         matches.add(new MatchEthernetType(0x0800L));
248         if (addOrRemove == NwConstants.ADD_FLOW) {
249             if (extSubnetId == NatConstants.INVALID_ID) {
250                 LOG.error("installSnatMissEntry : external subnet id is invalid.");
251                 return;
252             }
253             actionsInfo.add(new ActionSetFieldTunnelId(BigInteger.valueOf(extSubnetId)));
254             matches.add(new MatchMetadata(MetaDataUtil.getVpnIdMetadata(extSubnetId),
255                     MetaDataUtil.METADATA_MASK_VRFID));
256         }
257         matches.add(new MatchIpv4Destination(externalIp, "32"));
258         LOG.debug("installSnatMissEntry : Setting the tunnel to the list of action infos {}", actionsInfo);
259         actionsInfo.add(new ActionGroup(groupId));
260         instructions = new ArrayList<>();
261         instructions.add(new InstructionApplyActions(actionsInfo));
262         flowRef = getFlowRef(dpnId, NwConstants.L3_FIB_TABLE, routerId);
263         flowRef = flowRef + "inboundmiss" + externalIp;
264         syncFlow(dpnId, NwConstants.L3_FIB_TABLE, flowRef,  NatConstants.SNAT_FIB_FLOW_PRIORITY, flowRef,
265                 NwConstants.COOKIE_SNAT_TABLE, matches, instructions, addOrRemove);
266     }
267
268     protected void installInboundTerminatingServiceTblEntry(BigInteger dpnId, Long  routerId, String routerName,
269             String externalIp,  long extSubnetId, int addOrRemove) {
270         //Install the tunnel table entry in NAPT switch for inbound traffic to SNAP IP from a non a NAPT switch.
271         LOG.info("installInboundTerminatingServiceTblEntry : creating entry for Terminating Service Table "
272                 + "for switch {}, routerId {}", dpnId, routerId);
273         List<MatchInfo> matches = new ArrayList<>();
274         matches.add(MatchEthernetType.IPV4);
275         List<ActionInfo> actionsInfos = new ArrayList<>();
276         if (addOrRemove == NwConstants.ADD_FLOW) {
277             if (extSubnetId == NatConstants.INVALID_ID) {
278                 LOG.error("installInboundTerminatingServiceTblEntry : external subnet id is invalid.");
279                 return;
280             }
281             matches.add(new MatchTunnelId(BigInteger.valueOf(extSubnetId)));
282             ActionNxLoadMetadata actionLoadMeta = new ActionNxLoadMetadata(MetaDataUtil
283                     .getVpnIdMetadata(extSubnetId), LOAD_START, LOAD_END);
284             actionsInfos.add(actionLoadMeta);
285         }
286         actionsInfos.add(new ActionNxResubmit(NwConstants.INBOUND_NAPT_TABLE));
287         List<InstructionInfo> instructions = new ArrayList<>();
288         instructions.add(new InstructionApplyActions(actionsInfos));
289         String flowRef = getFlowRef(dpnId, NwConstants.INTERNAL_TUNNEL_TABLE, routerId.longValue());
290         flowRef = flowRef + "INBOUND";
291         syncFlow(dpnId,  NwConstants.INTERNAL_TUNNEL_TABLE, flowRef, NatConstants.SNAT_FIB_FLOW_PRIORITY, flowRef,
292                  NwConstants.COOKIE_SNAT_TABLE, matches, instructions, addOrRemove);
293
294     }
295
296     protected void installDefaultFibRouteForSNAT(BigInteger dpnId, Long extNetId, int addOrRemove) {
297
298         List<MatchInfo> matches = new ArrayList<>();
299         matches.add(MatchEthernetType.IPV4);
300         matches.add(new MatchMetadata(MetaDataUtil.getVpnIdMetadata(extNetId),
301                 MetaDataUtil.METADATA_MASK_VRFID));
302
303         List<InstructionInfo> instructions = new ArrayList<>();
304         instructions.add(new InstructionGotoTable(NwConstants.PSNAT_TABLE));
305
306         String flowRef = getFlowRef(dpnId, NwConstants.L3_FIB_TABLE, extNetId);
307         syncFlow(dpnId, NwConstants.L3_FIB_TABLE, flowRef, NatConstants.DEFAULT_DNAT_FLOW_PRIORITY, flowRef,
308                 NwConstants.COOKIE_SNAT_TABLE, matches, instructions, addOrRemove);
309     }
310
311     protected String getFlowRef(BigInteger dpnId, short tableId, long routerID) {
312         return new StringBuilder().append(NatConstants.NAPT_FLOWID_PREFIX).append(dpnId).append(NatConstants
313                 .FLOWID_SEPARATOR).append(tableId).append(NatConstants.FLOWID_SEPARATOR).append(routerID).toString();
314     }
315
316     protected void syncFlow(BigInteger dpId, short tableId, String flowId, int priority, String flowName,
317             BigInteger cookie, List<? extends MatchInfoBase> matches,
318             List<InstructionInfo> instructions, int addOrRemove) {
319         if (addOrRemove == NwConstants.DEL_FLOW) {
320             FlowEntity flowEntity = MDSALUtil.buildFlowEntity(dpId, tableId, flowId, priority, flowName,
321                     NatConstants.DEFAULT_IDLE_TIMEOUT, NatConstants.DEFAULT_IDLE_TIMEOUT, cookie, matches, null);
322             LOG.trace("syncFlow : Removing Acl Flow DpnId {}, flowId {}", dpId, flowId);
323             mdsalManager.removeFlow(flowEntity);
324         } else {
325             FlowEntity flowEntity = MDSALUtil.buildFlowEntity(dpId, tableId, flowId, priority, flowName,
326                     NatConstants.DEFAULT_IDLE_TIMEOUT, NatConstants.DEFAULT_IDLE_TIMEOUT, cookie, matches,
327                     instructions);
328             LOG.trace("syncFlow : Installing DpnId {}, flowId {}", dpId, flowId);
329             mdsalManager.installFlow(flowEntity);
330         }
331     }
332
333     protected long createGroupId(String groupIdKey) {
334         AllocateIdInput getIdInput = new AllocateIdInputBuilder()
335             .setPoolName(NatConstants.SNAT_IDPOOL_NAME).setIdKey(groupIdKey)
336             .build();
337         try {
338             Future<RpcResult<AllocateIdOutput>> result = idManager.allocateId(getIdInput);
339             RpcResult<AllocateIdOutput> rpcResult = result.get();
340             return rpcResult.getResult().getIdValue();
341         } catch (NullPointerException | InterruptedException | ExecutionException e) {
342             LOG.error("createGroupId: Exception while creating group with key : {}",groupIdKey, e);
343         }
344         return 0;
345     }
346
347     protected String getGroupIdKey(String routerName) {
348         return "snatmiss." + routerName;
349     }
350
351     protected String getTunnelInterfaceName(BigInteger srcDpId, BigInteger dstDpId) {
352         Class<? extends TunnelTypeBase> tunType = TunnelTypeVxlan.class;
353         RpcResult<GetTunnelInterfaceNameOutput> rpcResult;
354         try {
355             Future<RpcResult<GetTunnelInterfaceNameOutput>> result = itmManager
356                     .getTunnelInterfaceName(new GetTunnelInterfaceNameInputBuilder().setSourceDpid(srcDpId)
357                             .setDestinationDpid(dstDpId).setTunnelType(tunType).build());
358             rpcResult = result.get();
359             if (!rpcResult.isSuccessful()) {
360                 tunType = TunnelTypeGre.class ;
361                 result = itmManager.getTunnelInterfaceName(new GetTunnelInterfaceNameInputBuilder()
362                         .setSourceDpid(srcDpId)
363                         .setDestinationDpid(dstDpId)
364                         .setTunnelType(tunType)
365                         .build());
366                 rpcResult = result.get();
367                 if (!rpcResult.isSuccessful()) {
368                     LOG.warn("getTunnelInterfaceName : RPC Call to getTunnelInterfaceId returned with Errors {}",
369                             rpcResult.getErrors());
370                 } else {
371                     return rpcResult.getResult().getInterfaceName();
372                 }
373                 LOG.warn("getTunnelInterfaceName : RPC Call to getTunnelInterfaceId returned with Errors {}",
374                         rpcResult.getErrors());
375             } else {
376                 return rpcResult.getResult().getInterfaceName();
377             }
378         } catch (InterruptedException | ExecutionException | NullPointerException e) {
379             LOG.error("getTunnelInterfaceName : Exception when getting tunnel interface Id for tunnel "
380                     + "between {} and {}", srcDpId, dstDpId);
381         }
382         return null;
383     }
384
385     static int mostSignificantBit(int value) {
386         return 31 - Integer.numberOfLeadingZeros(value);
387     }
388 }