Support for SNAT and DNAT features in L3 forwarding services.
[vpnservice.git] / natservice / natservice-impl / src / main / java / org / opendaylight / vpnservice / natservice / internal / ExternalNetworkListener.java
diff --git a/natservice/natservice-impl/src/main/java/org/opendaylight/vpnservice/natservice/internal/ExternalNetworkListener.java b/natservice/natservice-impl/src/main/java/org/opendaylight/vpnservice/natservice/internal/ExternalNetworkListener.java
new file mode 100644 (file)
index 0000000..621aea2
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2016 Ericsson India Global Services Pvt Ltd. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.vpnservice.natservice.internal;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vpnservice.natservice.rev160111.ExternalNetworks;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.vpnservice.natservice.rev160111.external.networks.Networks;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
+
+import org.opendaylight.vpnservice.mdsalutil.ActionInfo;
+import org.opendaylight.vpnservice.mdsalutil.ActionType;
+import org.opendaylight.vpnservice.mdsalutil.FlowEntity;
+import org.opendaylight.vpnservice.mdsalutil.InstructionInfo;
+import org.opendaylight.vpnservice.mdsalutil.InstructionType;
+import org.opendaylight.vpnservice.mdsalutil.MDSALUtil;
+import org.opendaylight.vpnservice.mdsalutil.MatchFieldType;
+import org.opendaylight.vpnservice.mdsalutil.MatchInfo;
+import org.opendaylight.vpnservice.mdsalutil.MetaDataUtil;
+import org.opendaylight.vpnservice.mdsalutil.NwConstants;
+import org.opendaylight.vpnservice.mdsalutil.interfaces.IMdsalApiManager;
+import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.binding.api.DataChangeListener;
+import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import com.google.common.base.Optional;
+
+import org.opendaylight.yang.gen.v1.urn.opendaylight.l3vpn.rev130911.vpn.instance.op.data.VpnInstanceOpDataEntry;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.l3vpn.rev130911.vpn.instance.op.data.VpnInstanceOpDataEntryKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.l3vpn.rev130911.vpn.instance.op.data.vpn.instance.op.data.entry.VpnToDpnList;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.ArrayList;
+
+
+public class ExternalNetworkListener extends org.opendaylight.vpnservice.mdsalutil.AbstractDataChangeListener<Networks> implements AutoCloseable {
+    private static final Logger LOG = LoggerFactory.getLogger(ExternalNetworkListener.class);
+    private ListenerRegistration<DataChangeListener> listenerRegistration;
+    private final DataBroker broker;
+    private IMdsalApiManager mdsalManager;
+
+    public ExternalNetworkListener (final DataBroker db) {
+        super(Networks.class);
+        broker = db;
+        //registerListener(db);
+    }
+
+    @Override
+    public void close() throws Exception {
+        if (listenerRegistration != null) {
+            try {
+                listenerRegistration.close();
+            } catch (final Exception e) {
+                LOG.error("Error when cleaning up DataChangeListener.", e);
+            }
+            listenerRegistration = null;
+        }
+        LOG.info("ExternalNetwork Listener Closed");
+    }
+
+    private void registerListener(final DataBroker db) {
+        try {
+            listenerRegistration = db.registerDataChangeListener(LogicalDatastoreType.CONFIGURATION,
+                    getWildCardPath(), ExternalNetworkListener.this, AsyncDataBroker.DataChangeScope.SUBTREE);
+        } catch (final Exception e) {
+            LOG.error("External Network DataChange listener registration fail!", e);
+            throw new IllegalStateException("External Network registration Listener failed.", e);
+        }
+    }
+
+    private InstanceIdentifier<Networks> getWildCardPath() {
+        return InstanceIdentifier.create(ExternalNetworks.class).child(Networks.class);
+    }
+
+    public void setMdsalManager(IMdsalApiManager mdsalManager) {
+        this.mdsalManager = mdsalManager;
+    }
+
+    @Override
+    protected void add(final InstanceIdentifier<Networks> identifier,
+                       final Networks nw) {
+        LOG.trace("External Network add mapping method - key: " + identifier + ", value=" + nw );
+        processExternalNwAdd(identifier, nw);
+    }
+
+    @Override
+    protected void remove(InstanceIdentifier<Networks> identifier, Networks nw) {
+        LOG.trace("External Network remove mapping method - key: " + identifier + ", value=" + nw );
+        processExternalNwDel(identifier, nw);
+    }
+
+    @Override
+    protected void update(InstanceIdentifier<Networks> identifier, Networks original, Networks update) {
+        LOG.trace("External Network update mapping method - key: " + identifier + ", original=" + original + ", update=" + update );
+        //check if a new router has been added or an already existing router has been deleted from the external nw to router association
+        List<Uuid> oldRtrs = original.getRouterIds();
+        List<Uuid> newRtrs = update.getRouterIds();
+        if (oldRtrs != newRtrs) {
+            //handle both addition and removal of routers
+            for (Uuid rtr : newRtrs) {
+                if (oldRtrs.contains(rtr)) {
+                    oldRtrs.remove(rtr);
+                } else {
+                    // new router case
+                    //Routers added need to have the corresponding default Fib entry added to the switches in the router
+                    String routerId = rtr.getValue();
+                    addOrDelDefFibRouteToSNAT(routerId, true);
+
+                }
+            }
+
+            //Routers removed need to have the corresponding default Fib entry removed from the switches in the router
+            for (Uuid rtr : oldRtrs) {
+                String routerId = rtr.getValue();
+                addOrDelDefFibRouteToSNAT(routerId, false);
+            }
+        }
+    }
+
+    private void processExternalNwAdd(final InstanceIdentifier<Networks> identifier,
+                                      final Networks network) {
+        LOG.trace("Add event - key: {}, value: {}", identifier, network);
+        List<Uuid> routerList = network.getRouterIds();
+
+        if(routerList == null) {
+            LOG.debug("No routers associated with external network {}", identifier);
+            return;
+        }
+
+        for(Uuid router: routerList) {
+            String routerId = router.getValue();
+            addOrDelDefFibRouteToSNAT(routerId, true);
+        }
+    }
+
+    private void processExternalNwDel(final InstanceIdentifier<Networks> identifier,
+                                      final Networks network) {
+        LOG.trace("Add event - key: {}, value: {}", identifier, network);
+        List<Uuid> routerList = network.getRouterIds();
+
+        for(Uuid router: routerList) {
+            String routerId = router.getValue();
+            addOrDelDefFibRouteToSNAT(routerId, false);
+        }
+    }
+
+    private void addOrDelDefFibRouteToSNAT(String routerId, boolean create) {
+        //Router ID is used as the internal VPN's name, hence the vrf-id in VpnInstance Op DataStore
+        InstanceIdentifier<VpnInstanceOpDataEntry> id = NatUtil.getVpnInstanceOpDataIdentifier(routerId);
+        Optional<VpnInstanceOpDataEntry> vpnInstOp = NatUtil.read(broker, LogicalDatastoreType.OPERATIONAL, id);
+        if (vpnInstOp.isPresent()) {
+            List<VpnToDpnList> dpnListInVpn = vpnInstOp.get().getVpnToDpnList();
+            for (VpnToDpnList dpn : dpnListInVpn) {
+                BigInteger dpnId = dpn.getDpnId();
+                long vpnId = NatUtil.readVpnId(broker, vpnInstOp.get().getVrfId());
+                if (create == true) {
+                    installDefNATRouteInDPN(dpnId, vpnId);
+                } else {
+                    removeDefNATRouteInDPN(dpnId, vpnId);
+                }
+            }
+        }
+    }
+
+    private FlowEntity buildDefNATFlowEntity(BigInteger dpId, long vpnId) {
+
+        InetAddress defaultIP = null;
+
+        try {
+            defaultIP = InetAddress.getByName("0.0.0.0");
+
+        } catch (UnknownHostException e) {
+            LOG.error("UnknowHostException in buildDefNATFlowEntity. Failed  to build FIB Table Flow for Default Route to NAT table ");
+            return null;
+        }
+
+        List<MatchInfo> matches = new ArrayList<MatchInfo>();
+        matches.add(new MatchInfo(MatchFieldType.eth_type,
+                new long[] { 0x0800L }));
+
+        //add match for default route "0.0.0.0/0"
+        //matches.add(new MatchInfo(MatchFieldType.ipv4_src, new long[] {
+        //        NatUtil.getIpAddress(defaultIP.getAddress()), 0 }));
+
+        //add match for vrfid
+        matches.add(new MatchInfo(MatchFieldType.metadata, new BigInteger[] {
+                BigInteger.valueOf(vpnId), MetaDataUtil.METADATA_MASK_VRFID }));
+
+        List<InstructionInfo> instructions = new ArrayList<InstructionInfo>();
+        instructions.add(new InstructionInfo(InstructionType.goto_table, new long[] { NatConstants.PSNAT_TABLE }));
+
+        String flowRef = NatUtil.getFlowRef(dpId, NatConstants.L3_FIB_TABLE, defaultIP);
+
+        FlowEntity flowEntity = MDSALUtil.buildFlowEntity(dpId, NatConstants.L3_FIB_TABLE, flowRef,
+                NatConstants.DEFAULT_DNAT_FLOW_PRIORITY, flowRef, 0, 0,
+                NatConstants.COOKIE_DNAT_TABLE, matches, instructions);
+
+        return flowEntity;
+
+
+    }
+
+    private void installDefNATRouteInDPN(BigInteger dpnId, long vpnId) {
+        FlowEntity flowEntity = buildDefNATFlowEntity(dpnId, vpnId);
+        if(flowEntity == null) {
+            LOG.error("Flow entity received is NULL. Cannot proceed with installation of Default NAT flow");
+            return;
+        }
+        mdsalManager.installFlow(flowEntity);
+    }
+
+    private void removeDefNATRouteInDPN(BigInteger dpnId, long vpnId) {
+        FlowEntity flowEntity = buildDefNATFlowEntity(dpnId, vpnId);
+        if(flowEntity == null) {
+            LOG.error("Flow entity received is NULL. Cannot proceed with installation of Default NAT flow");
+            return;
+        }
+        mdsalManager.removeFlow(flowEntity);
+    }
+
+
+}
\ No newline at end of file