SNAT performance improvement for Controller-Based SNAT
[netvirt.git] / vpnservice / natservice / natservice-impl / src / main / java / org / opendaylight / netvirt / natservice / internal / NaptEventHandler.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
9 package org.opendaylight.netvirt.natservice.internal;
10
11 import com.google.common.util.concurrent.FutureCallback;
12 import com.google.common.util.concurrent.Futures;
13 import com.google.common.util.concurrent.JdkFutureAdapters;
14 import java.math.BigInteger;
15 import java.net.InetAddress;
16 import java.net.UnknownHostException;
17 import java.util.ArrayList;
18 import java.util.List;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.Future;
21 import javax.annotation.Nullable;
22 import javax.inject.Inject;
23 import javax.inject.Singleton;
24 import org.opendaylight.controller.liblldp.NetUtils;
25 import org.opendaylight.controller.liblldp.PacketException;
26 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
27 import org.opendaylight.genius.interfacemanager.globals.InterfaceInfo;
28 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
29 import org.opendaylight.genius.mdsalutil.ActionInfo;
30 import org.opendaylight.genius.mdsalutil.FlowEntity;
31 import org.opendaylight.genius.mdsalutil.FlowEntityBuilder;
32 import org.opendaylight.genius.mdsalutil.InstructionInfo;
33 import org.opendaylight.genius.mdsalutil.MDSALUtil;
34 import org.opendaylight.genius.mdsalutil.MatchInfo;
35 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
36 import org.opendaylight.genius.mdsalutil.NwConstants;
37 import org.opendaylight.genius.mdsalutil.actions.ActionOutput;
38 import org.opendaylight.genius.mdsalutil.actions.ActionPushVlan;
39 import org.opendaylight.genius.mdsalutil.actions.ActionSetDestinationIp;
40 import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldEthernetSource;
41 import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldTunnelId;
42 import org.opendaylight.genius.mdsalutil.actions.ActionSetFieldVlanVid;
43 import org.opendaylight.genius.mdsalutil.actions.ActionSetSourceIp;
44 import org.opendaylight.genius.mdsalutil.actions.ActionSetTcpDestinationPort;
45 import org.opendaylight.genius.mdsalutil.actions.ActionSetTcpSourcePort;
46 import org.opendaylight.genius.mdsalutil.actions.ActionSetUdpDestinationPort;
47 import org.opendaylight.genius.mdsalutil.actions.ActionSetUdpSourcePort;
48 import org.opendaylight.genius.mdsalutil.instructions.InstructionApplyActions;
49 import org.opendaylight.genius.mdsalutil.instructions.InstructionGotoTable;
50 import org.opendaylight.genius.mdsalutil.instructions.InstructionWriteMetadata;
51 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
52 import org.opendaylight.genius.mdsalutil.matches.MatchEthernetType;
53 import org.opendaylight.genius.mdsalutil.matches.MatchIpProtocol;
54 import org.opendaylight.genius.mdsalutil.matches.MatchIpv4Destination;
55 import org.opendaylight.genius.mdsalutil.matches.MatchIpv4Source;
56 import org.opendaylight.genius.mdsalutil.matches.MatchMetadata;
57 import org.opendaylight.genius.mdsalutil.matches.MatchTcpDestinationPort;
58 import org.opendaylight.genius.mdsalutil.matches.MatchTcpSourcePort;
59 import org.opendaylight.genius.mdsalutil.matches.MatchUdpDestinationPort;
60 import org.opendaylight.genius.mdsalutil.matches.MatchUdpSourcePort;
61 import org.opendaylight.genius.mdsalutil.packet.Ethernet;
62 import org.opendaylight.genius.mdsalutil.packet.IPv4;
63 import org.opendaylight.genius.mdsalutil.packet.TCP;
64 import org.opendaylight.genius.mdsalutil.packet.UDP;
65 import org.opendaylight.netvirt.elanmanager.api.IElanService;
66 import org.opendaylight.netvirt.natservice.internal.NaptPacketInHandler.NatPacketProcessingState;
67 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Uri;
68 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
69 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
70 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode;
71 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowId;
72 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table;
73 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.TableKey;
74 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow;
75 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.FlowKey;
76 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInput;
77 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowInputBuilder;
78 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.AddFlowOutput;
79 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.service.rev130819.SalFlowService;
80 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.FlowRef;
81 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
82 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.IfL2vlan;
83 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.GetInterfaceFromIfIndexInput;
84 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.GetInterfaceFromIfIndexInputBuilder;
85 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.GetInterfaceFromIfIndexOutput;
86 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.OdlInterfaceRpcService;
87 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef;
88 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeId;
89 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeRef;
90 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes;
91 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node;
92 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeBuilder;
93 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.NodeKey;
94 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.natservice.rev160111.ext.routers.Routers;
95 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService;
96 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInput;
97 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
98 import org.opendaylight.yangtools.yang.common.RpcResult;
99 import org.slf4j.Logger;
100 import org.slf4j.LoggerFactory;
101
102 @Singleton
103 public class NaptEventHandler {
104     private static final Logger LOG = LoggerFactory.getLogger(NaptEventHandler.class);
105     private final DataBroker dataBroker;
106     private static IMdsalApiManager mdsalManager;
107     private final PacketProcessingService pktService;
108     private final OdlInterfaceRpcService interfaceManagerRpc;
109     private final NaptManager naptManager;
110     private final IElanService elanManager;
111     private final IdManagerService idManager;
112     private final IInterfaceManager interfaceManager;
113     private static SalFlowService salFlowServiceRpc;
114
115     @Inject
116     public NaptEventHandler(final DataBroker dataBroker, final IMdsalApiManager mdsalManager,
117                             final NaptManager naptManager,
118                             final PacketProcessingService pktService,
119                             final OdlInterfaceRpcService interfaceManagerRpc,
120                             final IInterfaceManager interfaceManager,
121                             final IElanService elanManager,
122                             final IdManagerService idManager,
123                             final SalFlowService salFlowServiceRpc) {
124         this.dataBroker = dataBroker;
125         NaptEventHandler.mdsalManager = mdsalManager;
126         this.naptManager = naptManager;
127         this.pktService = pktService;
128         this.interfaceManagerRpc = interfaceManagerRpc;
129         this.interfaceManager = interfaceManager;
130         this.elanManager = elanManager;
131         this.idManager = idManager;
132         this.salFlowServiceRpc = salFlowServiceRpc;
133     }
134
135     // TODO Clean up the exception handling
136     @SuppressWarnings("checkstyle:IllegalCatch")
137     public void handleEvent(NAPTEntryEvent naptEntryEvent) {
138     /*
139             Flow programming logic of the OUTBOUND NAPT TABLE :
140             1) Get the internal IP address, port number, router ID from the event.
141             2) Use the NAPT service getExternalAddressMapping() to get the External IP and the port.
142             3) Build the flow for replacing the Internal IP and port with the External IP and port.
143               a) Write the matching criteria.
144               b) Match the router ID in the metadata.
145               d) Write the VPN ID to the metadata.
146               e) Write the other data.
147               f) Set the apply actions instruction with the action setfield.
148             4) Write the flow to the OUTBOUND NAPT Table and forward to FIB table for routing the traffic.
149
150             Flow programming logic of the INBOUND NAPT TABLE :
151             Same as Outbound table logic except that :
152             1) Build the flow for replacing the External IP and port with the Internal IP and port.
153             2) Match the VPN ID in the metadata.
154             3) Write the router ID to the metadata.
155             5) Write the flow to the INBOUND NAPT Table and forward to FIB table for routing the traffic.
156     */
157         try {
158             Long routerId = naptEntryEvent.getRouterId();
159             LOG.info("NAT Service : handleEvent() entry for {}:{}, routerID {},"
160                     + "TimeElapsed before procesing {}ms,isPktProcessed:{}",
161                     naptEntryEvent.getIpAddress(), naptEntryEvent.getPortNumber(), routerId,
162                     (System.currentTimeMillis() - naptEntryEvent.getObjectCreationTime()),
163                     naptEntryEvent.isPktProcessed());
164             //Get the DPN ID
165             BigInteger dpnId = NatUtil.getPrimaryNaptfromRouterId(dataBroker, routerId);
166             long bgpVpnId = NatConstants.INVALID_ID;
167             if (dpnId == null) {
168                 LOG.warn("NAT Service : dpnId is null. Assuming the router ID {} as the BGP VPN ID and proceeding....",
169                     routerId);
170                 bgpVpnId = routerId;
171                 LOG.debug("NAT Service : BGP VPN ID {}", bgpVpnId);
172                 String vpnName = NatUtil.getRouterName(dataBroker, bgpVpnId);
173                 String routerName = NatUtil.getRouterIdfromVpnInstance(dataBroker, vpnName);
174                 if (routerName == null) {
175                     LOG.error("NAT Service: Unable to find router for VpnName {}", vpnName);
176                     return;
177                 }
178                 routerId = NatUtil.getVpnId(dataBroker, routerName);
179                 LOG.debug("NAT Service : Router ID {}", routerId);
180                 dpnId = NatUtil.getPrimaryNaptfromRouterId(dataBroker, routerId);
181                 if (dpnId == null) {
182                     LOG.error("NAT Service : dpnId is null for the router {}", routerId);
183                     return;
184                 }
185             }
186             if (naptEntryEvent.getOperation() == NAPTEntryEvent.Operation.ADD) {
187                 LOG.debug("NAT Service : Inside Add operation of NaptEventHandler");
188
189                 // Build and install the NAPT translation flows in the Outbound and Inbound NAPT tables
190                 if (!naptEntryEvent.isPktProcessed()) {
191
192                     // Get the External Gateway MAC Address
193                     String extGwMacAddress = NatUtil.getExtGwMacAddFromRouterId(dataBroker, routerId);
194                     if (extGwMacAddress != null) {
195                         LOG.debug("NAT Service : External Gateway MAC address {} found for External Router ID {}",
196                                 extGwMacAddress, routerId);
197                     } else {
198                         LOG.error("NAT Service : No External Gateway MAC address found for External Router ID {}",
199                                 routerId);
200                         return;
201                     }
202                     //Get the external network ID from the ExternalRouter model
203                     Uuid networkId = NatUtil.getNetworkIdFromRouterId(dataBroker, routerId);
204                     if (networkId == null) {
205                         LOG.error("NAT Service : networkId is null");
206                         return;
207                     }
208
209                     //Get the VPN ID from the ExternalNetworks model
210                     Uuid vpnUuid = NatUtil.getVpnIdfromNetworkId(dataBroker, networkId);
211                     if (vpnUuid == null) {
212                         LOG.error("NAT Service : vpnUuid is null");
213                         return;
214                     }
215                     Long vpnId = NatUtil.getVpnId(dataBroker, vpnUuid.getValue());
216
217                     //Get the internal IpAddress, internal port number from the event
218                     String internalIpAddress = naptEntryEvent.getIpAddress();
219                     int internalPort = naptEntryEvent.getPortNumber();
220                     SessionAddress internalAddress = new SessionAddress(internalIpAddress, internalPort);
221                     NAPTEntryEvent.Protocol protocol = naptEntryEvent.getProtocol();
222
223                     //Get the external IP address for the corresponding internal IP address
224                     SessionAddress externalAddress =
225                             naptManager.getExternalAddressMapping(routerId, internalAddress,
226                                     naptEntryEvent.getProtocol());
227                     if (externalAddress == null) {
228                         LOG.error("NAT Service : externalAddress is null");
229                         return;
230                     }
231
232                     Long vpnIdFromExternalSubnet = getVpnIdFromExternalSubnet(dataBroker, routerId,
233                             externalAddress.getIpAddress());
234                     if (vpnIdFromExternalSubnet != NatConstants.INVALID_ID) {
235                         vpnId = vpnIdFromExternalSubnet;
236                     }
237
238                     // Added External Gateway MAC Address
239                     Future<RpcResult<AddFlowOutput>> addFlowResult =
240                             buildAndInstallNatFlowsOptionalRpc(dpnId, NwConstants.INBOUND_NAPT_TABLE, vpnId, routerId,
241                                     bgpVpnId, externalAddress, internalAddress, protocol, extGwMacAddress, true);
242                     final BigInteger finalDpnId = dpnId;
243                     final Long finalVpnId = vpnId;
244                     final Long finalRouterId = routerId;
245                     final long finalBgpVpnId = bgpVpnId;
246                     Futures.addCallback(JdkFutureAdapters.listenInPoolThread(addFlowResult),
247                             new FutureCallback<RpcResult<AddFlowOutput>>() {
248
249                                 @Override
250                                 public void onSuccess(@Nullable RpcResult<AddFlowOutput> result) {
251                                     LOG.debug("Configured inbound rule for {} to {}",
252                                              internalAddress, externalAddress);
253                                     Future<RpcResult<AddFlowOutput>> addFlowResult =
254                                             buildAndInstallNatFlowsOptionalRpc(finalDpnId,
255                                                     NwConstants.OUTBOUND_NAPT_TABLE, finalVpnId, finalRouterId,
256                                                     finalBgpVpnId, internalAddress, externalAddress, protocol,
257                                                     extGwMacAddress, true);
258                                     Futures.addCallback(JdkFutureAdapters.listenInPoolThread(addFlowResult),
259                                             new FutureCallback<RpcResult<AddFlowOutput>>() {
260
261                                             @Override
262                                             public void onSuccess(@Nullable RpcResult<AddFlowOutput> result) {
263                                                 LOG.debug("Configured outbound rule, sending packet out"
264                                                         + "from {} to {}", internalAddress, externalAddress);
265                                                 prepareAndSendPacketOut(naptEntryEvent, finalRouterId);
266                                             }
267
268                                             @Override
269                                             public void onFailure(Throwable throwable) {
270                                                 LOG.error("Error configuring outbound SNAT flows using RPC for "
271                                                                 + "SNAT connection from {} to {}",
272                                                                   internalAddress, externalAddress);
273                                             }
274                                         });
275                                 }
276
277                                 @Override
278                                 public void onFailure(Throwable throwable) {
279                                     LOG.error("Error configuring inbound SNAT flows using RPC for SNAT connection"
280                                             + " from {} to {}", internalAddress, externalAddress);
281                                 }
282                             });
283                     String key = naptEntryEvent.getIpAddress() + ":" + naptEntryEvent.getPortNumber();
284                     NatPacketProcessingState state = NaptPacketInHandler.INCOMING_PACKET_MAP.get(key);
285                     state.setFlowInstalledTime(System.currentTimeMillis());
286                 } else {
287                     prepareAndSendPacketOut(naptEntryEvent, routerId);
288                 }
289                 LOG.info("NAT Service : handleNaptEvent() exited for {}:{},routerID:{},"
290                         + "ElapsedTime packet:{}ms,isPktProcessed:{} ",
291                         naptEntryEvent.getIpAddress(), naptEntryEvent.getPortNumber(), routerId,
292                         (System.currentTimeMillis() - naptEntryEvent.getObjectCreationTime()),
293                         naptEntryEvent.isPktProcessed());
294             } else {
295                 LOG.debug("NAT Service : Inside delete Operation of NaptEventHandler");
296                 removeNatFlows(dpnId, NwConstants.INBOUND_NAPT_TABLE, routerId, naptEntryEvent.getIpAddress(),
297                     naptEntryEvent.getPortNumber());
298                 LOG.info("NAT Service : handleNaptEvent() exited for removeEvent for IP {}, port {}, routerID : {}",
299                         naptEntryEvent.getIpAddress(), naptEntryEvent.getPortNumber(), routerId);
300             }
301         } catch (Exception e) {
302             LOG.error("NAT Service :Exception in NaptEventHandler.handleEvent() payload {}", naptEntryEvent, e);
303         }
304     }
305
306     private void prepareAndSendPacketOut(NAPTEntryEvent naptEntryEvent, Long routerId) {
307         //Send Packetout - tcp or udp packets which got punted to controller.
308         BigInteger metadata = naptEntryEvent.getPacketReceived().getMatch().getMetadata().getMetadata();
309         byte[] inPayload = naptEntryEvent.getPacketReceived().getPayload();
310         Ethernet ethPkt = new Ethernet();
311         if (inPayload != null) {
312             try {
313                 ethPkt.deserialize(inPayload, 0, inPayload.length * NetUtils.NumBitsInAByte);
314             } catch (PacketException e) {
315                 LOG.warn("NAT Service : Failed to decode Packet", e);
316                 return;
317             }
318         }
319
320         long portTag = MetaDataUtil.getLportFromMetadata(metadata).intValue();
321         LOG.debug("NAT Service : portTag from incoming packet is {}", portTag);
322         String interfaceName = getInterfaceNameFromTag(portTag);
323         LOG.debug("NAT Service : interfaceName fetched from portTag is {}", interfaceName);
324         org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508
325                 .interfaces.Interface iface = null;
326         int vlanId = 0;
327         iface = interfaceManager.getInterfaceInfoFromConfigDataStore(interfaceName);
328         if (iface == null) {
329             LOG.error("NAT Service : Unable to read interface {} from config DataStore", interfaceName);
330             return;
331         }
332         List<ActionInfo> actionInfos = new ArrayList<>();
333         IfL2vlan ifL2vlan = iface.getAugmentation(IfL2vlan.class);
334         if (ifL2vlan != null && ifL2vlan.getVlanId() != null) {
335             vlanId = ifL2vlan.getVlanId().getValue() == null ? 0 : ifL2vlan.getVlanId().getValue();
336         }
337         InterfaceInfo infInfo = interfaceManager.getInterfaceInfoFromOperationalDataStore(interfaceName);
338         if (infInfo == null) {
339             LOG.error("NAT Service : error in getting interfaceInfo");
340             return;
341         }
342
343         byte[] pktOut = buildNaptPacketOut(ethPkt);
344         if (ethPkt.getEtherType() != (short) NwConstants.ETHTYPE_802_1Q) {
345             // VLAN Access port
346             LOG.debug("NAT Service : vlanId is {}", vlanId);
347             if (vlanId != 0) {
348                 // Push vlan
349                 actionInfos.add(new ActionPushVlan(0));
350                 actionInfos.add(new ActionSetFieldVlanVid(1, vlanId));
351             } else {
352                 LOG.debug("NAT Service : No vlanId {}, may be untagged", vlanId);
353             }
354         } else {
355             // VLAN Trunk Port
356             LOG.debug("NAT Service : This is VLAN Trunk port case - need not do VLAN tagging again");
357         }
358         if (pktOut != null) {
359             String routerName = NatUtil.getRouterName(dataBroker, routerId);
360             long tunId = NatUtil.getTunnelIdForNonNaptToNaptFlow(dataBroker, elanManager, idManager, routerId,
361                     routerName);
362             sendNaptPacketOut(pktOut, infInfo, actionInfos, tunId);
363         } else {
364             LOG.warn("NAT Service : Unable to send Packet Out");
365         }
366     }
367
368     public static void buildAndInstallNatFlows(BigInteger dpnId, short tableId, long vpnId, long routerId,
369                                                long bgpVpnId, SessionAddress actualSourceAddress,
370                                                SessionAddress translatedSourceAddress,
371                                                NAPTEntryEvent.Protocol protocol, String extGwMacAddress) {
372         buildAndInstallNatFlowsOptionalRpc(dpnId, tableId, vpnId, routerId, bgpVpnId, actualSourceAddress,
373                 translatedSourceAddress, protocol, extGwMacAddress, false);
374     }
375
376     public static Future<RpcResult<AddFlowOutput>> buildAndInstallNatFlowsOptionalRpc(
377             BigInteger dpnId, short tableId, long vpnId, long routerId, long bgpVpnId,
378             SessionAddress actualSourceAddress, SessionAddress translatedSourceAddress,
379             NAPTEntryEvent.Protocol protocol, String extGwMacAddress,
380             boolean sendRpc) {
381         LOG.debug("NAT Service : Build and install NAPT flows for table {} for "
382             + "dpnId {} and routerId {}", tableId, dpnId, routerId);
383         //Build the flow for replacing the actual IP and port with the translated IP and port.
384         int idleTimeout = 0;
385         if (tableId == NwConstants.OUTBOUND_NAPT_TABLE) {
386             idleTimeout = NatConstants.DEFAULT_NAPT_IDLE_TIMEOUT;
387         }
388         long intranetVpnId;
389         if (bgpVpnId != NatConstants.INVALID_ID) {
390             intranetVpnId = bgpVpnId;
391         } else {
392             intranetVpnId = routerId;
393         }
394         LOG.debug("NAT Service : Intranet VPN ID {} Router ID {}", intranetVpnId, routerId);
395         String translatedIp = translatedSourceAddress.getIpAddress();
396         int translatedPort = translatedSourceAddress.getPortNumber();
397         String actualIp = actualSourceAddress.getIpAddress();
398         int actualPort = actualSourceAddress.getPortNumber();
399         String switchFlowRef =
400             NatUtil.getNaptFlowRef(dpnId, tableId, String.valueOf(routerId), actualIp, actualPort);
401
402         FlowEntity snatFlowEntity = new FlowEntityBuilder()
403             .setDpnId(dpnId)
404             .setTableId(tableId)
405             .setFlowId(switchFlowRef)
406             .setPriority(NatConstants.DEFAULT_NAPT_FLOW_PRIORITY)
407             .setFlowName(NatConstants.NAPT_FLOW_NAME)
408             .setIdleTimeOut(idleTimeout)
409             .setHardTimeOut(0)
410             .setCookie(NatUtil.getCookieNaptFlow(routerId))
411             .setMatchInfoList(buildAndGetMatchInfo(actualIp, actualPort, tableId, protocol, intranetVpnId))
412             .setInstructionInfoList(buildAndGetSetActionInstructionInfo(translatedIp, translatedPort,
413                                             intranetVpnId, vpnId, tableId, protocol, extGwMacAddress))
414             .setSendFlowRemFlag(true)
415             .build();
416
417         LOG.debug("NAT Service : Installing the NAPT flow in the table {} for the switch with the DPN ID {} ",
418             tableId, dpnId);
419
420         // Install flows using RPC to prevent race with future packet-out that depends on this flow
421         Future<RpcResult<AddFlowOutput>> addFlowResult = null;
422         if (sendRpc) {
423             Flow flow = snatFlowEntity.getFlowBuilder().build();
424             NodeRef nodeRef = getNodeRef(dpnId);
425             FlowRef flowRef = getFlowRef(dpnId, flow);
426             AddFlowInput addFlowInput = new AddFlowInputBuilder(flow).setFlowRef(flowRef).setNode(nodeRef).build();
427             long startTime = System.currentTimeMillis();
428             addFlowResult = salFlowServiceRpc.addFlow(addFlowInput);
429             LOG.debug("NAT Service : Time elapsed for salFlowServiceRpc table {}: {}ms ", tableId,
430                     (System.currentTimeMillis() - startTime));
431          // Keep flow installation through MDSAL as well to be able to handle switch failures
432             startTime = System.currentTimeMillis();
433             mdsalManager.installFlow(snatFlowEntity);
434             LOG.debug("NAT Service : Time elapsed for installFlow table {}: {}ms ", tableId,
435                     (System.currentTimeMillis() - startTime));
436         } else {
437             long startTime = System.currentTimeMillis();
438             mdsalManager.syncInstallFlow(snatFlowEntity, 1);
439             LOG.debug("NAT Service : Time elapsed for syncInstallFlow table {}: {}ms ", tableId,
440                     (System.currentTimeMillis() - startTime));
441         }
442         LOG.trace("NAT Service : Exited buildAndInstallNatflows");
443
444         return addFlowResult;
445     }
446
447     private static Node buildInventoryDpnNode(BigInteger dpnId) {
448         NodeId nodeId = new NodeId("openflow:" + dpnId);
449         Node nodeDpn = new NodeBuilder().setId(nodeId).setKey(new NodeKey(nodeId)).build();
450         return nodeDpn;
451     }
452
453     private static NodeRef getNodeRef(BigInteger dpnId) {
454         NodeId nodeId = new NodeId("openflow:" + dpnId);
455         return new NodeRef(InstanceIdentifier.builder(Nodes.class)
456                 .child(Node.class, new NodeKey(nodeId)).toInstance());
457     }
458
459     public static FlowRef getFlowRef(BigInteger dpId, Flow flow) {
460         FlowKey flowKey = new FlowKey(new FlowId(flow.getId()));
461         Node nodeDpn = buildInventoryDpnNode(dpId);
462         InstanceIdentifier<Flow> flowInstanceId =
463                 InstanceIdentifier.builder(Nodes.class)
464                 .child(Node.class, nodeDpn.getKey()).augmentation(FlowCapableNode.class)
465                 .child(Table.class, new TableKey(flow.getTableId()))
466                 .child(Flow.class, flowKey)
467                 .build();
468         return new FlowRef(flowInstanceId);
469     }
470
471     private static List<MatchInfo> buildAndGetMatchInfo(String ip, int port, short tableId,
472                                                         NAPTEntryEvent.Protocol protocol, long segmentId) {
473         MatchInfo ipMatchInfo = null;
474         MatchInfo portMatchInfo = null;
475         MatchInfo protocolMatchInfo = null;
476         InetAddress ipAddress = null;
477         String ipAddressAsString = null;
478         try {
479             ipAddress = InetAddress.getByName(ip);
480             ipAddressAsString = ipAddress.getHostAddress();
481
482         } catch (UnknownHostException e) {
483             LOG.error("NAT Service : UnknowHostException in buildAndGetMatchInfo. Failed  to build NAPT Flow for "
484                 + "ip {}", ipAddress);
485             return null;
486         }
487
488         MatchInfo metaDataMatchInfo = null;
489         if (tableId == NwConstants.OUTBOUND_NAPT_TABLE) {
490             ipMatchInfo = new MatchIpv4Source(ipAddressAsString, "32");
491             if (protocol == NAPTEntryEvent.Protocol.TCP) {
492                 protocolMatchInfo = MatchIpProtocol.TCP;
493                 portMatchInfo = new MatchTcpSourcePort(port);
494             } else if (protocol == NAPTEntryEvent.Protocol.UDP) {
495                 protocolMatchInfo = MatchIpProtocol.UDP;
496                 portMatchInfo = new MatchUdpSourcePort(port);
497             }
498             metaDataMatchInfo =
499                     new MatchMetadata(MetaDataUtil.getVpnIdMetadata(segmentId), MetaDataUtil.METADATA_MASK_VRFID);
500         } else {
501             ipMatchInfo = new MatchIpv4Destination(ipAddressAsString, "32");
502             if (protocol == NAPTEntryEvent.Protocol.TCP) {
503                 protocolMatchInfo = MatchIpProtocol.TCP;
504                 portMatchInfo = new MatchTcpDestinationPort(port);
505             } else if (protocol == NAPTEntryEvent.Protocol.UDP) {
506                 protocolMatchInfo = MatchIpProtocol.UDP;
507                 portMatchInfo = new MatchUdpDestinationPort(port);
508             }
509             //metaDataMatchInfo = new MatchMetadata(BigInteger.valueOf(vpnId), MetaDataUtil.METADATA_MASK_VRFID);
510         }
511         ArrayList<MatchInfo> matchInfo = new ArrayList<>();
512         matchInfo.add(MatchEthernetType.IPV4);
513         matchInfo.add(ipMatchInfo);
514         matchInfo.add(protocolMatchInfo);
515         matchInfo.add(portMatchInfo);
516         if (tableId == NwConstants.OUTBOUND_NAPT_TABLE) {
517             matchInfo.add(metaDataMatchInfo);
518         }
519         return matchInfo;
520     }
521
522     private static List<InstructionInfo> buildAndGetSetActionInstructionInfo(String ipAddress, int port,
523                                                                              long segmentId, long vpnId,
524                                                                              short tableId,
525                                                                              NAPTEntryEvent.Protocol protocol,
526                                                                              String extGwMacAddress) {
527         ActionInfo ipActionInfo = null;
528         ActionInfo macActionInfo = null;
529         ActionInfo portActionInfo = null;
530         ArrayList<ActionInfo> listActionInfo = new ArrayList<>();
531         ArrayList<InstructionInfo> instructionInfo = new ArrayList<>();
532         switch (tableId) {
533             case NwConstants.OUTBOUND_NAPT_TABLE:
534                 ipActionInfo = new ActionSetSourceIp(ipAddress);
535                 // Added External Gateway MAC Address
536                 macActionInfo = new ActionSetFieldEthernetSource(new MacAddress(extGwMacAddress));
537                 if (protocol == NAPTEntryEvent.Protocol.TCP) {
538                     portActionInfo = new ActionSetTcpSourcePort(port);
539                 } else if (protocol == NAPTEntryEvent.Protocol.UDP) {
540                     portActionInfo = new ActionSetUdpSourcePort(port);
541                 }
542                 // reset the split-horizon bit to allow traffic from tunnel to be sent back to the provider port
543                 instructionInfo.add(new InstructionWriteMetadata(MetaDataUtil.getVpnIdMetadata(vpnId),
544                     MetaDataUtil.METADATA_MASK_VRFID.or(MetaDataUtil.METADATA_MASK_SH_FLAG)));
545                 break;
546
547             case NwConstants.INBOUND_NAPT_TABLE:
548                 ipActionInfo = new ActionSetDestinationIp(ipAddress);
549                 if (protocol == NAPTEntryEvent.Protocol.TCP) {
550                     portActionInfo = new ActionSetTcpDestinationPort(port);
551                 } else if (protocol == NAPTEntryEvent.Protocol.UDP) {
552                     portActionInfo = new ActionSetUdpDestinationPort(port);
553                 }
554                 instructionInfo.add(new InstructionWriteMetadata(
555                         MetaDataUtil.getVpnIdMetadata(segmentId), MetaDataUtil.METADATA_MASK_VRFID));
556                 break;
557
558             default:
559                 LOG.error("NAT Service : Neither OUTBOUND_NAPT_TABLE nor INBOUND_NAPT_TABLE matches with "
560                     + "input table id {}", tableId);
561                 return null;
562         }
563
564         listActionInfo.add(ipActionInfo);
565         listActionInfo.add(portActionInfo);
566         if (macActionInfo != null) {
567             listActionInfo.add(macActionInfo);
568             LOG.debug("NAT Service : External GW MAC Address {} is found  ", macActionInfo);
569         }
570         instructionInfo.add(new InstructionApplyActions(listActionInfo));
571         instructionInfo.add(new InstructionGotoTable(NwConstants.NAPT_PFIB_TABLE));
572
573         return instructionInfo;
574     }
575
576     void removeNatFlows(BigInteger dpnId, short tableId ,long segmentId, String ip, int port) {
577         if (dpnId == null || dpnId.equals(BigInteger.ZERO)) {
578             LOG.error("NAT Service : DPN ID {} is invalid" , dpnId);
579         }
580         LOG.debug("NAT Service : Remove NAPT flows for dpnId {}, segmentId {}, ip {} and port {} ",
581             dpnId, segmentId, ip, port);
582
583         //Build the flow with the port IP and port as the match info.
584         String switchFlowRef = NatUtil.getNaptFlowRef(dpnId, tableId, String.valueOf(segmentId), ip, port);
585         FlowEntity snatFlowEntity = NatUtil.buildFlowEntity(dpnId, tableId, switchFlowRef);
586         LOG.debug("NAT Service : Remove the flow in the table {} for the switch with the DPN ID {}",
587             NwConstants.INBOUND_NAPT_TABLE, dpnId);
588         long startTime = System.currentTimeMillis();
589         mdsalManager.removeFlow(snatFlowEntity);
590         LOG.debug("NAT Service : Time Taken for removeFlow:{}ms", (System.currentTimeMillis() - startTime));
591
592     }
593
594     protected byte[] buildNaptPacketOut(Ethernet etherPkt) {
595         LOG.debug("NAT Service : About to build Napt Packet Out");
596         if (etherPkt.getPayload() instanceof IPv4) {
597             byte[] rawPkt;
598             IPv4 ipPkt = (IPv4) etherPkt.getPayload();
599             if (ipPkt.getPayload() instanceof TCP || ipPkt.getPayload() instanceof UDP) {
600                 try {
601                     rawPkt = etherPkt.serialize();
602                     return rawPkt;
603                 } catch (PacketException e2) {
604                     LOG.error("failed to build NAPT Packet out ", e2);
605                     return null;
606                 }
607             } else {
608                 LOG.error("NAT Service : Unable to build NaptPacketOut since its neither TCP nor UDP");
609                 return null;
610             }
611         }
612         LOG.error("NAT Service : Unable to build NaptPacketOut since its not IPv4 packet");
613         return null;
614     }
615
616     private void sendNaptPacketOut(byte[] pktOut, InterfaceInfo infInfo, List<ActionInfo> actionInfos, Long tunId) {
617         LOG.trace("NAT Service: Sending packet out DpId {}, interfaceInfo {}", infInfo.getDpId(), infInfo);
618         // set inPort, and action as OFPP_TABLE so that it starts from table 0 (lowest table as per spec)
619         actionInfos.add(new ActionSetFieldTunnelId(2, BigInteger.valueOf(tunId)));
620         actionInfos.add(new ActionOutput(3, new Uri("0xfffffff9")));
621         NodeConnectorRef inPort = MDSALUtil.getNodeConnRef(infInfo.getDpId(), String.valueOf(infInfo.getPortNo()));
622         LOG.debug("NAT Service : inPort for packetout is being set to {}", String.valueOf(infInfo.getPortNo()));
623         TransmitPacketInput output = MDSALUtil.getPacketOut(actionInfos, pktOut, infInfo.getDpId().longValue(), inPort);
624         LOG.debug("NAT Service: Transmitting packet: {}, inPort {}", output, inPort);
625         this.pktService.transmitPacket(output);
626     }
627
628     private String getInterfaceNameFromTag(long portTag) {
629         String interfaceName = null;
630         GetInterfaceFromIfIndexInput input =
631             new GetInterfaceFromIfIndexInputBuilder().setIfIndex((int) portTag).build();
632         Future<RpcResult<GetInterfaceFromIfIndexOutput>> futureOutput =
633             interfaceManagerRpc.getInterfaceFromIfIndex(input);
634         try {
635             GetInterfaceFromIfIndexOutput output = futureOutput.get().getResult();
636             interfaceName = output.getInterfaceName();
637         } catch (InterruptedException | ExecutionException e) {
638             LOG.error("NAT Service : Error while retrieving the interfaceName from tag using "
639                 + "getInterfaceFromIfIndex RPC");
640         }
641         LOG.trace("NAT Service : Returning interfaceName {} for tag {} form getInterfaceNameFromTag",
642             interfaceName, portTag);
643         return interfaceName;
644     }
645
646     private long getVpnIdFromExternalSubnet(DataBroker dataBroker, Long routerId, String externalIpAddress) {
647         String routerName = NatUtil.getRouterName(dataBroker, routerId);
648         if (routerName != null) {
649             Routers extRouter = NatUtil.getRoutersFromConfigDS(dataBroker, routerName);
650             if (extRouter != null) {
651                 return NatUtil.getExternalSubnetVpnIdForRouterExternalIp(dataBroker, externalIpAddress, extRouter);
652             }
653         }
654
655         return NatConstants.INVALID_ID;
656     }
657 }