Support for SNAT and DNAT features in L3 forwarding services.
[vpnservice.git] / natservice / natservice-impl / src / main / java / org / opendaylight / vpnservice / natservice / internal / NaptEventHandler.java
1 /*
2  * Copyright (c) 2016 Ericsson India Global Services Pvt Ltd. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.vpnservice.natservice.internal;
10
11 import org.opendaylight.vpnservice.mdsalutil.*;
12 import org.opendaylight.vpnservice.mdsalutil.interfaces.IMdsalApiManager;
13 import org.opendaylight.vpnservice.mdsalutil.packet.IPProtocols;
14 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
15 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
16 import java.math.BigInteger;
17 import java.net.InetAddress;
18 import java.net.UnknownHostException;
19 import java.util.List;
20 import java.util.ArrayList;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23
24 public class NaptEventHandler {
25     private NaptManager naptManager;
26     private static final Logger LOG = LoggerFactory.getLogger(NaptEventHandler.class);
27     private static IMdsalApiManager mdsalManager;
28     private DataBroker dataBroker;
29
30     public NaptEventHandler(final DataBroker dataBroker) {
31         this.dataBroker = dataBroker;
32     }
33
34     public void setMdsalManager(IMdsalApiManager mdsalManager) {
35         this.mdsalManager = mdsalManager;
36     }
37
38     public void setNaptManager(NaptManager naptManager) {
39         this.naptManager = naptManager;
40     }
41
42
43     public void handleEvent(NAPTEntryEvent naptEntryEvent){
44     /*
45             Flow programming logic of the OUTBOUND NAPT TABLE :
46             1) Get the internal IP address, port number, router ID from the event.
47             2) Use the NAPT service getExternalAddressMapping() to get the External IP and the port.
48             3) Build the flow for replacing the Internal IP and port with the External IP and port.
49               a) Write the matching criteria.
50               b) Match the router ID in the metadata.
51               d) Write the VPN ID to the metadata.
52               e) Write the other data.
53               f) Set the apply actions instruction with the action setfield.
54             4) Write the flow to the OUTBOUND NAPT Table and forward to FIB table for routing the traffic.
55
56             Flow programming logic of the INBOUND NAPT TABLE :
57             Same as Outbound table logic except that :
58             1) Build the flow for replacing the External IP and port with the Internal IP and port.
59             2) Match the VPN ID in the metadata.
60             3) Write the router ID to the metadata.
61             5) Write the flow to the INBOUND NAPT Table and forward to FIB table for routing the traffic.
62     */
63         Long routerId = naptEntryEvent.getRouterId();
64         LOG.info("NAT Service : handleEvent() entry for IP {}, port {}, routerID {}", naptEntryEvent.getIpAddress(), naptEntryEvent.getPortNumber(), routerId);
65         BigInteger dpnId = NatUtil.getPrimaryNaptfromRouterId(dataBroker, routerId);
66         if(dpnId == null ){
67             LOG.error("NAT Service : dpnId is null");
68             return;
69         }
70         if(naptEntryEvent.getOperation() == NAPTEntryEvent.Operation.ADD) {
71             LOG.debug("NAT Service : Inside Add operation of NaptEventHandler");
72
73             //Get the external network ID from the ExternalRouter model
74             Uuid networkId = NatUtil.getNetworkIdFromRouterId(dataBroker, routerId);
75             if(networkId == null ){
76                 LOG.error("NAT Service : networkId is null");
77                 return;
78             }
79
80             //Get the VPN ID from the ExternalNetworks model
81             Uuid vpnUuid = NatUtil.getVpnIdfromNetworkId(dataBroker, networkId);
82             if(vpnUuid == null ){
83                 LOG.error("NAT Service : vpnUuid is null");
84                 return;
85             }
86             Long vpnId = NatUtil.getVpnId(dataBroker, vpnUuid.getValue());
87
88             //Get the internal IpAddress, internal port number from the event
89             String internalIpAddress = naptEntryEvent.getIpAddress();
90             int internalPort = naptEntryEvent.getPortNumber();
91             SessionAddress internalAddress = new SessionAddress(internalIpAddress, internalPort);
92             NAPTEntryEvent.Protocol protocol = naptEntryEvent.getProtocol();
93
94             //Get the external IP address for the corresponding internal IP address
95             SessionAddress externalAddress = naptManager.getExternalAddressMapping(routerId, internalAddress, naptEntryEvent.getProtocol());
96             if(externalAddress == null ){
97                 LOG.error("NAT Service : externalAddress is null");
98                 return;
99             }
100
101             //Build and install the NAPT translation flows in the Outbound and Inbound NAPT tables
102             buildAndInstallNatFlows(dpnId, NatConstants.OUTBOUND_NAPT_TABLE, vpnId, routerId, internalAddress, externalAddress, protocol);
103             buildAndInstallNatFlows(dpnId, NatConstants.INBOUND_NAPT_TABLE, vpnId, routerId, externalAddress, internalAddress, protocol);
104
105         }else{
106             LOG.debug("NAT Service : Inside delete Operation of NaptEventHandler");
107             removeNatFlows(dpnId, routerId, naptEntryEvent.getIpAddress(), naptEntryEvent.getPortNumber());
108         }
109
110         LOG.info("NAT Service : handleNaptEvent() exited for IP, port, routerID : {}", naptEntryEvent.getIpAddress(), naptEntryEvent.getPortNumber(), routerId);
111     }
112
113     public static void buildAndInstallNatFlows(BigInteger dpnId, short tableId, long vpnId, long routerId, SessionAddress actualSourceAddress,
114                                          SessionAddress translatedSourceAddress, NAPTEntryEvent.Protocol protocol){
115         LOG.debug("NAT Service : Build and install NAPT flows in InBound and OutBound tables for dpnId {} and routerId {}", dpnId, routerId);
116         //Build the flow for replacing the actual IP and port with the translated IP and port.
117         String actualIp = actualSourceAddress.getIpAddress();
118         int actualPort = actualSourceAddress.getPortNumber();
119         String translatedIp = translatedSourceAddress.getIpAddress();
120         String translatedPort = String.valueOf(translatedSourceAddress.getPortNumber());
121         int idleTimeout=0;
122         if(tableId == NatConstants.OUTBOUND_NAPT_TABLE) {
123             idleTimeout = NatConstants.DEFAULT_NAPT_IDLE_TIMEOUT;
124         }
125         long metaDataValue = routerId;
126         String switchFlowRef = NatUtil.getNaptFlowRef(dpnId, tableId, String.valueOf(metaDataValue), actualIp, actualPort);
127
128         FlowEntity snatFlowEntity = MDSALUtil.buildFlowEntity(dpnId, tableId, switchFlowRef, NatConstants.DEFAULT_NAPT_FLOW_PRIORITY, NatConstants.NAPT_FLOW_NAME,
129                 idleTimeout, 0, NatUtil.getCookieNaptFlow(metaDataValue), buildAndGetMatchInfo(actualIp, actualPort, tableId, protocol, routerId, vpnId),
130                 buildAndGetSetActionInstructionInfo(translatedIp, translatedPort, routerId, vpnId, tableId, protocol));
131
132         snatFlowEntity.setSendFlowRemFlag(true);
133
134         LOG.debug("NAT Service : Installing the NAPT flow in the table {} for the switch with the DPN ID {} ", tableId, dpnId);
135         mdsalManager.installFlow(snatFlowEntity);
136     }
137
138     private static List<MatchInfo> buildAndGetMatchInfo(String ip, int port, short tableId, NAPTEntryEvent.Protocol protocol, long segmentId, long vpnId){
139         MatchInfo ipMatchInfo = null;
140         MatchInfo portMatchInfo = null;
141         MatchInfo protocolMatchInfo = null;
142         InetAddress ipAddress = null;
143         String ipAddressAsString = null;
144         try {
145             ipAddress = InetAddress.getByName(ip);
146             ipAddressAsString = ipAddress.getHostAddress();
147
148         } catch (UnknownHostException e) {
149             LOG.error("NAT Service : UnknowHostException in buildAndGetMatchInfo. Failed  to build NAPT Flow for  ip {}", ipAddress);
150             return null;
151         }
152
153         MatchInfo metaDataMatchInfo;
154         if(tableId == NatConstants.OUTBOUND_NAPT_TABLE){
155             ipMatchInfo = new MatchInfo(MatchFieldType.ipv4_source, new String[] {ipAddressAsString, "32" });
156             if(protocol == NAPTEntryEvent.Protocol.TCP) {
157                 protocolMatchInfo = new MatchInfo(MatchFieldType.ip_proto, new long[] {IPProtocols.TCP.intValue()});
158                 portMatchInfo = new MatchInfo(MatchFieldType.tcp_src, new long[]{port});
159             } else if(protocol == NAPTEntryEvent.Protocol.UDP) {
160                 protocolMatchInfo = new MatchInfo(MatchFieldType.ip_proto, new long[] {IPProtocols.UDP.intValue()});
161                 portMatchInfo = new MatchInfo(MatchFieldType.udp_src, new long[]{port});
162             }
163             metaDataMatchInfo = new MatchInfo(MatchFieldType.metadata, new BigInteger[]{BigInteger.valueOf(segmentId), MetaDataUtil.METADATA_MASK_VRFID});
164         }else{
165             ipMatchInfo = new MatchInfo(MatchFieldType.ipv4_destination, new String[] {ipAddressAsString, "32" });         
166             if(protocol == NAPTEntryEvent.Protocol.TCP) {
167                 protocolMatchInfo = new MatchInfo(MatchFieldType.ip_proto, new long[] {IPProtocols.TCP.intValue()});
168                 portMatchInfo = new MatchInfo(MatchFieldType.tcp_dst, new long[]{port});
169             } else if(protocol == NAPTEntryEvent.Protocol.UDP) {
170                 protocolMatchInfo = new MatchInfo(MatchFieldType.ip_proto, new long[] {IPProtocols.UDP.intValue()});
171                 portMatchInfo = new MatchInfo(MatchFieldType.udp_dst, new long[]{port});
172             }
173             metaDataMatchInfo = new MatchInfo(MatchFieldType.metadata, new BigInteger[]{BigInteger.valueOf(vpnId), MetaDataUtil.METADATA_MASK_VRFID});
174         }
175         ArrayList<MatchInfo> matchInfo = new ArrayList<>();
176         matchInfo.add(new MatchInfo(MatchFieldType.eth_type, new long[] { 0x0800L }));
177         matchInfo.add(ipMatchInfo);
178         matchInfo.add(protocolMatchInfo);
179         matchInfo.add(portMatchInfo);
180         matchInfo.add(metaDataMatchInfo);
181         return matchInfo;
182     }
183
184     private static List<InstructionInfo> buildAndGetSetActionInstructionInfo(String ipAddress, String port, long routerId, long vpnId, short tableId, NAPTEntryEvent.Protocol protocol) {
185         ActionInfo ipActionInfo = null;
186         ActionInfo portActionInfo = null;
187         ArrayList<ActionInfo> listActionInfo = new ArrayList<>();
188         ArrayList<InstructionInfo> instructionInfo = new ArrayList<>();
189
190         if(tableId == NatConstants.OUTBOUND_NAPT_TABLE){
191             ipActionInfo = new ActionInfo(ActionType.set_source_ip, new String[] {ipAddress});
192             if(protocol == NAPTEntryEvent.Protocol.TCP) {
193                portActionInfo = new ActionInfo( ActionType.set_tcp_source_port, new String[] {port});
194             } else if(protocol == NAPTEntryEvent.Protocol.UDP) {
195                portActionInfo = new ActionInfo( ActionType.set_udp_source_port, new String[] {port});
196             }
197             instructionInfo.add(new InstructionInfo(InstructionType.write_metadata, new BigInteger[]{BigInteger.valueOf(vpnId), MetaDataUtil.METADATA_MASK_VRFID}));
198         }else{
199             ipActionInfo = new ActionInfo(ActionType.set_destination_ip, new String[] {ipAddress});
200             if(protocol == NAPTEntryEvent.Protocol.TCP) {
201                portActionInfo = new ActionInfo( ActionType.set_tcp_destination_port, new String[] {port});
202             } else if(protocol == NAPTEntryEvent.Protocol.UDP) {
203                portActionInfo = new ActionInfo( ActionType.set_udp_destination_port, new String[] {port});
204             }
205             instructionInfo.add(new InstructionInfo(InstructionType.write_metadata, new BigInteger[]{BigInteger.valueOf(routerId), MetaDataUtil.METADATA_MASK_VRFID}));
206         }
207
208         listActionInfo.add(ipActionInfo);
209         listActionInfo.add(portActionInfo);
210
211         instructionInfo.add(new InstructionInfo(InstructionType.apply_actions, listActionInfo));
212         instructionInfo.add(new InstructionInfo(InstructionType.goto_table, new long[] { NatConstants.NAPT_PFIB_TABLE }));
213
214         return instructionInfo;
215     }
216
217     private void removeNatFlows(BigInteger dpnId, long routerId, String externalIp, int externalPort){
218         LOG.debug("NAT Service : Remove NAPT flows for dpnId {} and routerId {}", dpnId, routerId);
219
220         //Build the flow with the externalPort IP and port as the match info.
221         String switchFlowRef = NatUtil.getNaptFlowRef(dpnId, NatConstants.INBOUND_NAPT_TABLE, String.valueOf(routerId), externalIp, externalPort);
222         FlowEntity snatFlowEntity = NatUtil.buildFlowEntity(dpnId, NatConstants.INBOUND_NAPT_TABLE, switchFlowRef);
223         LOG.debug("NAT Service : Remove the flow in the table {} for the switch with the DPN ID {}", NatConstants.INBOUND_NAPT_TABLE, dpnId);
224         mdsalManager.removeFlow(snatFlowEntity);
225
226     }
227
228 }