2 * Copyright (c) 2017 Red Hat, Inc. 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.natservice.internal;
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;
57 public abstract class AbstractSnatService implements SnatServiceListener {
58 private static final Logger LOG = LoggerFactory.getLogger(AbstractSnatService.class);
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());
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;
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;
84 protected DataBroker getDataBroker() {
89 LOG.info("{} init", getClass().getSimpleName());
93 LOG.debug("AbstractSnatService Closed");
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);
102 * Primary switch handled separately since the pseudo port created may
103 * not be present in the switch list on delete.
105 handleSnat(routers, primarySwitchId, primarySwitchId, addOrRemove);
106 for (BigInteger dpnId : switches) {
107 if (primarySwitchId != dpnId) {
108 handleSnat(routers, primarySwitchId, dpnId, addOrRemove);
116 public boolean handleSnat(Routers routers, BigInteger primarySwitchId, BigInteger dpnId, int addOrRemove) {
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);
124 LOG.debug("handleSnat : Handle NAPT switch {}", dpnId);
125 installSnatCommonEntriesForNaptSwitch(routers, dpnId, addOrRemove);
126 installSnatSpecificEntriesForNaptSwitch(routers, dpnId, addOrRemove);
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 {}",
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);
150 installInboundFibEntry(dpnId, externalIp, routerName, routerId, extSubnetId, addOrRemove);
151 installInboundTerminatingServiceTblEntry(dpnId, routerId, routerName, externalIp, extSubnetId, addOrRemove);
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 {}",
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);
172 installDefaultFibRouteForSNAT(dpnId, routerId, addOrRemove);
173 installSnatMissEntry(dpnId, routerId, routerName, primarySwitchId, externalIp, extSubnetId, addOrRemove);
176 protected abstract void installSnatSpecificEntriesForNaptSwitch(Routers routers, BigInteger dpnId,
179 protected abstract void installSnatSpecificEntriesForNonNaptSwitch(Routers routers, BigInteger dpnId,
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.");
191 matches.add(new MatchMetadata(MetaDataUtil.getVpnIdMetadata(extSubnetId),
192 MetaDataUtil.METADATA_MASK_VRFID));
194 matches.add(new MatchIpv4Destination(externalIp, "32"));
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));
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);
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);
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,
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));
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.");
253 actionsInfo.add(new ActionSetFieldTunnelId(BigInteger.valueOf(extSubnetId)));
254 matches.add(new MatchMetadata(MetaDataUtil.getVpnIdMetadata(extSubnetId),
255 MetaDataUtil.METADATA_MASK_VRFID));
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);
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.");
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);
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);
296 protected void installDefaultFibRouteForSNAT(BigInteger dpnId, Long extNetId, int addOrRemove) {
298 List<MatchInfo> matches = new ArrayList<>();
299 matches.add(MatchEthernetType.IPV4);
300 matches.add(new MatchMetadata(MetaDataUtil.getVpnIdMetadata(extNetId),
301 MetaDataUtil.METADATA_MASK_VRFID));
303 List<InstructionInfo> instructions = new ArrayList<>();
304 instructions.add(new InstructionGotoTable(NwConstants.PSNAT_TABLE));
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);
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();
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);
325 FlowEntity flowEntity = MDSALUtil.buildFlowEntity(dpId, tableId, flowId, priority, flowName,
326 NatConstants.DEFAULT_IDLE_TIMEOUT, NatConstants.DEFAULT_IDLE_TIMEOUT, cookie, matches,
328 LOG.trace("syncFlow : Installing DpnId {}, flowId {}", dpId, flowId);
329 mdsalManager.installFlow(flowEntity);
333 protected long createGroupId(String groupIdKey) {
334 AllocateIdInput getIdInput = new AllocateIdInputBuilder()
335 .setPoolName(NatConstants.SNAT_IDPOOL_NAME).setIdKey(groupIdKey)
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);
347 protected String getGroupIdKey(String routerName) {
348 return "snatmiss." + routerName;
351 protected String getTunnelInterfaceName(BigInteger srcDpId, BigInteger dstDpId) {
352 Class<? extends TunnelTypeBase> tunType = TunnelTypeVxlan.class;
353 RpcResult<GetTunnelInterfaceNameOutput> rpcResult;
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)
366 rpcResult = result.get();
367 if (!rpcResult.isSuccessful()) {
368 LOG.warn("getTunnelInterfaceName : RPC Call to getTunnelInterfaceId returned with Errors {}",
369 rpcResult.getErrors());
371 return rpcResult.getResult().getInterfaceName();
373 LOG.warn("getTunnelInterfaceName : RPC Call to getTunnelInterfaceId returned with Errors {}",
374 rpcResult.getErrors());
376 return rpcResult.getResult().getInterfaceName();
378 } catch (InterruptedException | ExecutionException | NullPointerException e) {
379 LOG.error("getTunnelInterfaceName : Exception when getting tunnel interface Id for tunnel "
380 + "between {} and {}", srcDpId, dstDpId);
385 static int mostSignificantBit(int value) {
386 return 31 - Integer.numberOfLeadingZeros(value);