2 * Copyright (c) 2016 Ericsson India Global Services Pvt Ltd. 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
9 package org.opendaylight.netvirt.natservice.internal;
11 import java.math.BigInteger;
12 import java.net.InetAddress;
13 import java.net.UnknownHostException;
14 import java.util.ArrayList;
15 import java.util.List;
16 import java.util.concurrent.ExecutionException;
17 import java.util.concurrent.Future;
18 import org.opendaylight.controller.liblldp.NetUtils;
19 import org.opendaylight.controller.liblldp.PacketException;
20 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
21 import org.opendaylight.genius.interfacemanager.globals.InterfaceInfo;
22 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
23 import org.opendaylight.genius.mdsalutil.ActionInfo;
24 import org.opendaylight.genius.mdsalutil.ActionType;
25 import org.opendaylight.genius.mdsalutil.FlowEntity;
26 import org.opendaylight.genius.mdsalutil.InstructionInfo;
27 import org.opendaylight.genius.mdsalutil.InstructionType;
28 import org.opendaylight.genius.mdsalutil.MDSALUtil;
29 import org.opendaylight.genius.mdsalutil.MatchFieldType;
30 import org.opendaylight.genius.mdsalutil.MatchInfo;
31 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
32 import org.opendaylight.genius.mdsalutil.NwConstants;
33 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
34 import org.opendaylight.genius.mdsalutil.packet.Ethernet;
35 import org.opendaylight.genius.mdsalutil.packet.IPProtocols;
36 import org.opendaylight.genius.mdsalutil.packet.IPv4;
37 import org.opendaylight.genius.mdsalutil.packet.TCP;
38 import org.opendaylight.genius.mdsalutil.packet.UDP;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rev160406.IfL2vlan;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.GetInterfaceFromIfIndexInput;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.GetInterfaceFromIfIndexInputBuilder;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.GetInterfaceFromIfIndexOutput;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.rpcs.rev160406.OdlInterfaceRpcService;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.NodeConnectorRef;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.PacketProcessingService;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.packet.service.rev130709.TransmitPacketInput;
48 import org.opendaylight.yangtools.yang.common.RpcResult;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
52 public class NaptEventHandler {
53 private static final Logger LOG = LoggerFactory.getLogger(NaptEventHandler.class);
54 private final DataBroker dataBroker;
55 private static IMdsalApiManager mdsalManager;
56 private final PacketProcessingService pktService;
57 private final OdlInterfaceRpcService interfaceManagerRpc;
58 private final NaptManager naptManager;
59 private IInterfaceManager interfaceManager;
61 public NaptEventHandler(final DataBroker dataBroker, final IMdsalApiManager mdsalManager,
62 final NaptManager naptManager,
63 final PacketProcessingService pktService,
64 final OdlInterfaceRpcService interfaceManagerRpc,
65 final IInterfaceManager interfaceManager) {
66 this.dataBroker = dataBroker;
67 NaptEventHandler.mdsalManager = mdsalManager;
68 this.naptManager = naptManager;
69 this.pktService = pktService;
70 this.interfaceManagerRpc = interfaceManagerRpc;
71 this.interfaceManager = interfaceManager;
74 public void handleEvent(NAPTEntryEvent naptEntryEvent){
76 Flow programming logic of the OUTBOUND NAPT TABLE :
77 1) Get the internal IP address, port number, router ID from the event.
78 2) Use the NAPT service getExternalAddressMapping() to get the External IP and the port.
79 3) Build the flow for replacing the Internal IP and port with the External IP and port.
80 a) Write the matching criteria.
81 b) Match the router ID in the metadata.
82 d) Write the VPN ID to the metadata.
83 e) Write the other data.
84 f) Set the apply actions instruction with the action setfield.
85 4) Write the flow to the OUTBOUND NAPT Table and forward to FIB table for routing the traffic.
87 Flow programming logic of the INBOUND NAPT TABLE :
88 Same as Outbound table logic except that :
89 1) Build the flow for replacing the External IP and port with the Internal IP and port.
90 2) Match the VPN ID in the metadata.
91 3) Write the router ID to the metadata.
92 5) Write the flow to the INBOUND NAPT Table and forward to FIB table for routing the traffic.
95 Long routerId = naptEntryEvent.getRouterId();
96 LOG.info("NAT Service : handleEvent() entry for IP {}, port {}, routerID {}", naptEntryEvent.getIpAddress(), naptEntryEvent.getPortNumber(), routerId);
99 BigInteger dpnId = NatUtil.getPrimaryNaptfromRouterId(dataBroker, routerId);
100 long bgpVpnId = NatConstants.INVALID_ID;
102 LOG.warn("NAT Service : dpnId is null. Assuming the router ID {} as the BGP VPN ID and proceeding....", routerId);
104 LOG.debug("NAT Service : BGP VPN ID {}", bgpVpnId);
105 String vpnName = NatUtil.getRouterName(dataBroker, bgpVpnId);
106 String routerName = NatUtil.getRouterIdfromVpnInstance(dataBroker, vpnName);
107 if (routerName == null) {
108 LOG.error("NAT Service: Unable to find router for VpnName {}", vpnName);
111 routerId = NatUtil.getVpnId(dataBroker, routerName);
112 LOG.debug("NAT Service : Router ID {}", routerId);
113 dpnId = NatUtil.getPrimaryNaptfromRouterId(dataBroker, routerId);
115 LOG.error("NAT Service : dpnId is null for the router {}", routerId);
119 if(naptEntryEvent.getOperation() == NAPTEntryEvent.Operation.ADD) {
120 LOG.debug("NAT Service : Inside Add operation of NaptEventHandler");
122 //Get the external network ID from the ExternalRouter model
123 Uuid networkId = NatUtil.getNetworkIdFromRouterId(dataBroker, routerId);
124 if(networkId == null ){
125 LOG.error("NAT Service : networkId is null");
129 //Get the VPN ID from the ExternalNetworks model
130 Uuid vpnUuid = NatUtil.getVpnIdfromNetworkId(dataBroker, networkId);
131 if(vpnUuid == null ){
132 LOG.error("NAT Service : vpnUuid is null");
135 Long vpnId = NatUtil.getVpnId(dataBroker, vpnUuid.getValue());
137 //Get the internal IpAddress, internal port number from the event
138 String internalIpAddress = naptEntryEvent.getIpAddress();
139 int internalPort = naptEntryEvent.getPortNumber();
140 SessionAddress internalAddress = new SessionAddress(internalIpAddress, internalPort);
141 NAPTEntryEvent.Protocol protocol = naptEntryEvent.getProtocol();
143 //Get the external IP address for the corresponding internal IP address
144 SessionAddress externalAddress = naptManager.getExternalAddressMapping(routerId, internalAddress, naptEntryEvent.getProtocol());
145 if(externalAddress == null ){
146 if(externalAddress == null){
147 LOG.error("NAT Service : externalAddress is null");
151 //Build and install the NAPT translation flows in the Outbound and Inbound NAPT tables
152 if(!naptEntryEvent.isPktProcessed()) {
153 buildAndInstallNatFlows(dpnId, NwConstants.OUTBOUND_NAPT_TABLE, vpnId, routerId, bgpVpnId, internalAddress, externalAddress, protocol);
154 buildAndInstallNatFlows(dpnId, NwConstants.INBOUND_NAPT_TABLE, vpnId, routerId, bgpVpnId, externalAddress, internalAddress, protocol);
157 //Send Packetout - tcp or udp packets which got punted to controller.
158 BigInteger metadata = naptEntryEvent.getPacketReceived().getMatch().getMetadata().getMetadata();
159 byte[] inPayload = naptEntryEvent.getPacketReceived().getPayload();
160 Ethernet ethPkt = new Ethernet();
161 if (inPayload != null) {
163 ethPkt.deserialize(inPayload, 0, inPayload.length * NetUtils.NumBitsInAByte);
164 } catch (Exception e) {
165 LOG.warn("Failed to decode Packet", e);
171 long portTag = MetaDataUtil.getLportFromMetadata(metadata).intValue();
172 LOG.debug("NAT Service : portTag from incoming packet is {}", portTag);
173 String interfaceName = getInterfaceNameFromTag(portTag);
174 LOG.debug("NAT Service : interfaceName fetched from portTag is {}", interfaceName);
175 org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.interfaces.Interface iface = null;
177 iface = interfaceManager.getInterfaceInfoFromConfigDataStore(interfaceName);
179 LOG.error("NAT Service : Unable to read interface {} from config DataStore", interfaceName);
182 IfL2vlan ifL2vlan = iface.getAugmentation(IfL2vlan.class);
183 if(ifL2vlan != null && ifL2vlan.getVlanId() !=null){
184 vlanId = ifL2vlan.getVlanId().getValue() == null ? 0 : ifL2vlan.getVlanId().getValue();
186 InterfaceInfo infInfo = interfaceManager.getInterfaceInfoFromOperationalDataStore(interfaceName);
188 LOG.debug("NAT Service : portName fetched from interfaceManager is {}", infInfo.getPortName());
191 byte[] pktOut = buildNaptPacketOut(ethPkt);
193 List<ActionInfo> actionInfos = new ArrayList<ActionInfo>();
194 if (ethPkt.getPayload() instanceof IPv4) {
195 IPv4 ipPkt = (IPv4) ethPkt.getPayload();
196 if ((ipPkt.getPayload() instanceof TCP) || (ipPkt.getPayload() instanceof UDP) ) {
197 if (ethPkt.getEtherType() != (short)NwConstants.ETHTYPE_802_1Q) {
199 if(infInfo != null) {
200 LOG.debug("NAT Service : vlanId is {}", vlanId);
203 actionInfos.add(new ActionInfo(ActionType.push_vlan, new String[] {}, 0));
204 actionInfos.add(new ActionInfo(ActionType.set_field_vlan_vid,
205 new String[] { Long.toString(vlanId) }, 1));
207 LOG.debug("NAT Service : No vlanId {}, may be untagged", vlanId);
210 LOG.error("NAT Service : error in getting interfaceInfo");
215 LOG.debug("NAT Service : This is VLAN Trunk port case - need not do VLAN tagging again");
220 sendNaptPacketOut(pktOut, infInfo, actionInfos, routerId);
222 LOG.warn("NAT Service : Unable to send Packet Out");
226 LOG.debug("NAT Service : Inside delete Operation of NaptEventHandler");
227 removeNatFlows(dpnId, NwConstants.INBOUND_NAPT_TABLE, routerId, naptEntryEvent.getIpAddress(), naptEntryEvent.getPortNumber());
230 LOG.info("NAT Service : handleNaptEvent() exited for IP, port, routerID : {}", naptEntryEvent.getIpAddress(), naptEntryEvent.getPortNumber(), routerId);
231 } catch (Exception e){
232 LOG.error("NAT Service :Exception in NaptEventHandler.handleEvent() payload {}", naptEntryEvent,e);
236 public static void buildAndInstallNatFlows(BigInteger dpnId, short tableId, long vpnId, long routerId, long bgpVpnId, SessionAddress actualSourceAddress,
237 SessionAddress translatedSourceAddress, NAPTEntryEvent.Protocol protocol){
238 LOG.debug("NAT Service : Build and install NAPT flows in InBound and OutBound tables for dpnId {} and routerId {}", dpnId, routerId);
239 //Build the flow for replacing the actual IP and port with the translated IP and port.
240 String actualIp = actualSourceAddress.getIpAddress();
241 int actualPort = actualSourceAddress.getPortNumber();
242 String translatedIp = translatedSourceAddress.getIpAddress();
243 String translatedPort = String.valueOf(translatedSourceAddress.getPortNumber());
245 if(tableId == NwConstants.OUTBOUND_NAPT_TABLE) {
246 idleTimeout = NatConstants.DEFAULT_NAPT_IDLE_TIMEOUT;
248 long metaDataValue = routerId;
249 String switchFlowRef = NatUtil.getNaptFlowRef(dpnId, tableId, String.valueOf(metaDataValue), actualIp, actualPort);
252 if(bgpVpnId != NatConstants.INVALID_ID){
253 intranetVpnId = bgpVpnId;
255 intranetVpnId = routerId;
257 LOG.debug("NAT Service : Intranet VPN ID {}", intranetVpnId);
258 LOG.debug("NAT Service : Router ID {}", routerId);
259 FlowEntity snatFlowEntity = MDSALUtil.buildFlowEntity(dpnId, tableId, switchFlowRef, NatConstants.DEFAULT_NAPT_FLOW_PRIORITY, NatConstants.NAPT_FLOW_NAME,
260 idleTimeout, 0, NatUtil.getCookieNaptFlow(metaDataValue), buildAndGetMatchInfo(actualIp, actualPort, tableId, protocol, intranetVpnId, vpnId),
261 buildAndGetSetActionInstructionInfo(translatedIp, translatedPort, intranetVpnId, vpnId, tableId, protocol));
263 snatFlowEntity.setSendFlowRemFlag(true);
265 LOG.debug("NAT Service : Installing the NAPT flow in the table {} for the switch with the DPN ID {} ", tableId, dpnId);
266 mdsalManager.syncInstallFlow(snatFlowEntity, 1);
267 LOG.trace("NAT Service : Exited buildAndInstallNatflows");
270 private static List<MatchInfo> buildAndGetMatchInfo(String ip, int port, short tableId, NAPTEntryEvent.Protocol protocol, long segmentId, long vpnId){
271 MatchInfo ipMatchInfo = null;
272 MatchInfo portMatchInfo = null;
273 MatchInfo protocolMatchInfo = null;
274 InetAddress ipAddress = null;
275 String ipAddressAsString = null;
277 ipAddress = InetAddress.getByName(ip);
278 ipAddressAsString = ipAddress.getHostAddress();
280 } catch (UnknownHostException e) {
281 LOG.error("NAT Service : UnknowHostException in buildAndGetMatchInfo. Failed to build NAPT Flow for ip {}", ipAddress);
285 MatchInfo metaDataMatchInfo = null;
286 if(tableId == NwConstants.OUTBOUND_NAPT_TABLE){
287 ipMatchInfo = new MatchInfo(MatchFieldType.ipv4_source, new String[] {ipAddressAsString, "32" });
288 if(protocol == NAPTEntryEvent.Protocol.TCP) {
289 protocolMatchInfo = new MatchInfo(MatchFieldType.ip_proto, new long[] {IPProtocols.TCP.intValue()});
290 portMatchInfo = new MatchInfo(MatchFieldType.tcp_src, new long[]{port});
291 } else if(protocol == NAPTEntryEvent.Protocol.UDP) {
292 protocolMatchInfo = new MatchInfo(MatchFieldType.ip_proto, new long[] {IPProtocols.UDP.intValue()});
293 portMatchInfo = new MatchInfo(MatchFieldType.udp_src, new long[]{port});
295 metaDataMatchInfo = new MatchInfo(MatchFieldType.metadata,
296 new BigInteger[] { MetaDataUtil.getVpnIdMetadata(segmentId), MetaDataUtil.METADATA_MASK_VRFID });
298 ipMatchInfo = new MatchInfo(MatchFieldType.ipv4_destination, new String[] {ipAddressAsString, "32" });
299 if(protocol == NAPTEntryEvent.Protocol.TCP) {
300 protocolMatchInfo = new MatchInfo(MatchFieldType.ip_proto, new long[] {IPProtocols.TCP.intValue()});
301 portMatchInfo = new MatchInfo(MatchFieldType.tcp_dst, new long[]{port});
302 } else if(protocol == NAPTEntryEvent.Protocol.UDP) {
303 protocolMatchInfo = new MatchInfo(MatchFieldType.ip_proto, new long[] {IPProtocols.UDP.intValue()});
304 portMatchInfo = new MatchInfo(MatchFieldType.udp_dst, new long[]{port});
306 //metaDataMatchInfo = new MatchInfo(MatchFieldType.metadata, new BigInteger[]{BigInteger.valueOf(vpnId), MetaDataUtil.METADATA_MASK_VRFID});
308 ArrayList<MatchInfo> matchInfo = new ArrayList<>();
309 matchInfo.add(new MatchInfo(MatchFieldType.eth_type, new long[] { 0x0800L }));
310 matchInfo.add(ipMatchInfo);
311 matchInfo.add(protocolMatchInfo);
312 matchInfo.add(portMatchInfo);
313 if(tableId == NwConstants.OUTBOUND_NAPT_TABLE){
314 matchInfo.add(metaDataMatchInfo);
319 private static List<InstructionInfo> buildAndGetSetActionInstructionInfo(String ipAddress, String port, long segmentId, long vpnId, short tableId, NAPTEntryEvent.Protocol protocol) {
320 ActionInfo ipActionInfo = null;
321 ActionInfo portActionInfo = null;
322 ArrayList<ActionInfo> listActionInfo = new ArrayList<>();
323 ArrayList<InstructionInfo> instructionInfo = new ArrayList<>();
325 if(tableId == NwConstants.OUTBOUND_NAPT_TABLE){
326 ipActionInfo = new ActionInfo(ActionType.set_source_ip, new String[] {ipAddress});
327 if(protocol == NAPTEntryEvent.Protocol.TCP) {
328 portActionInfo = new ActionInfo( ActionType.set_tcp_source_port, new String[] {port});
329 } else if(protocol == NAPTEntryEvent.Protocol.UDP) {
330 portActionInfo = new ActionInfo( ActionType.set_udp_source_port, new String[] {port});
332 // reset the split-horizon bit to allow traffic from tunnel to be
333 // sent back to the provider port
334 instructionInfo.add(new InstructionInfo(InstructionType.write_metadata,
335 new BigInteger[] { MetaDataUtil.getVpnIdMetadata(vpnId),
336 MetaDataUtil.METADATA_MASK_VRFID.or(MetaDataUtil.METADATA_MASK_SH_FLAG) }));
338 ipActionInfo = new ActionInfo(ActionType.set_destination_ip, new String[] {ipAddress});
339 if(protocol == NAPTEntryEvent.Protocol.TCP) {
340 portActionInfo = new ActionInfo( ActionType.set_tcp_destination_port, new String[] {port});
341 } else if(protocol == NAPTEntryEvent.Protocol.UDP) {
342 portActionInfo = new ActionInfo( ActionType.set_udp_destination_port, new String[] {port});
344 instructionInfo.add(new InstructionInfo(InstructionType.write_metadata,
345 new BigInteger[] { MetaDataUtil.getVpnIdMetadata(segmentId), MetaDataUtil.METADATA_MASK_VRFID }));
348 listActionInfo.add(ipActionInfo);
349 listActionInfo.add(portActionInfo);
351 instructionInfo.add(new InstructionInfo(InstructionType.apply_actions, listActionInfo));
352 instructionInfo.add(new InstructionInfo(InstructionType.goto_table, new long[] { NwConstants.NAPT_PFIB_TABLE
355 return instructionInfo;
358 void removeNatFlows(BigInteger dpnId, short tableId ,long segmentId, String ip, int port){
359 if(dpnId == null || dpnId.equals(BigInteger.ZERO)){
360 LOG.error("NAT Service : DPN ID {} is invalid" , dpnId);
362 LOG.debug("NAT Service : Remove NAPT flows for dpnId {}, segmentId {}, ip {} and port {} ", dpnId, segmentId, ip, port);
364 //Build the flow with the port IP and port as the match info.
365 String switchFlowRef = NatUtil.getNaptFlowRef(dpnId, tableId, String.valueOf(segmentId), ip, port);
366 FlowEntity snatFlowEntity = NatUtil.buildFlowEntity(dpnId, tableId, switchFlowRef);
367 LOG.debug("NAT Service : Remove the flow in the table {} for the switch with the DPN ID {}", NwConstants.INBOUND_NAPT_TABLE, dpnId);
368 mdsalManager.removeFlow(snatFlowEntity);
372 protected byte[] buildNaptPacketOut(Ethernet etherPkt) {
374 LOG.debug("NAT Service : About to build Napt Packet Out");
375 if (etherPkt.getPayload() instanceof IPv4) {
377 IPv4 ipPkt = (IPv4) etherPkt.getPayload();
378 if ((ipPkt.getPayload() instanceof TCP) || (ipPkt.getPayload() instanceof UDP) ) {
380 rawPkt = etherPkt.serialize();
382 } catch (PacketException e2) {
383 // TODO Auto-generated catch block
384 e2.printStackTrace();
388 LOG.error("NAT Service : Unable to build NaptPacketOut since its neither TCP nor UDP");
392 LOG.error("NAT Service : Unable to build NaptPacketOut since its not IPv4 packet");
397 private void sendNaptPacketOut(byte[] pktOut, InterfaceInfo infInfo, List<ActionInfo> actionInfos, Long routerId) {
398 LOG.trace("NAT Service: Sending packet out DpId {}, interfaceInfo {}", infInfo.getDpId(), infInfo);
399 // set in_port, and action as OFPP_TABLE so that it starts from table 0 (lowest table as per spec)
400 actionInfos.add(new ActionInfo(ActionType.set_field_tunnel_id, new BigInteger[] {
401 BigInteger.valueOf(routerId)}, 2));
402 actionInfos.add(new ActionInfo(ActionType.output, new String[] { "0xfffffff9" }, 3));
403 NodeConnectorRef in_port = MDSALUtil.getNodeConnRef(infInfo.getDpId(), String.valueOf(infInfo.getPortNo()));
404 LOG.debug("NAT Service : in_port for packetout is being set to {}", String.valueOf(infInfo.getPortNo()));
405 TransmitPacketInput output = MDSALUtil.getPacketOut(actionInfos, pktOut, infInfo.getDpId().longValue(), in_port);
406 LOG.trace("NAT Service: Transmitting packet: {}",output);
407 this.pktService.transmitPacket(output);
410 private String getInterfaceNameFromTag(long portTag) {
411 String interfaceName = null;
412 GetInterfaceFromIfIndexInput input = new GetInterfaceFromIfIndexInputBuilder().setIfIndex(new Integer((int)portTag)).build();
413 Future<RpcResult<GetInterfaceFromIfIndexOutput>> futureOutput = interfaceManagerRpc.getInterfaceFromIfIndex(input);
415 GetInterfaceFromIfIndexOutput output = futureOutput.get().getResult();
416 interfaceName = output.getInterfaceName();
417 } catch (InterruptedException | ExecutionException e) {
418 LOG.error("NAT Service : Error while retrieving the interfaceName from tag using getInterfaceFromIfIndex RPC");
420 LOG.trace("NAT Service : Returning interfaceName {} for tag {} form getInterfaceNameFromTag", interfaceName, portTag);
421 return interfaceName;