[ONOS-4426] Upgrade Vtn Module when access same network segment

Change-Id: Id0d00e9d0e93d1baf4ff20560469316fee5a3186
diff --git a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/manager/impl/VtnManager.java b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/manager/impl/VtnManager.java
index 06c3769..8f46dbe 100644
--- a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/manager/impl/VtnManager.java
+++ b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/manager/impl/VtnManager.java
@@ -18,6 +18,7 @@
 import static org.onosproject.net.flow.instructions.ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST;
 import static org.slf4j.LoggerFactory.getLogger;
 
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -27,6 +28,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
@@ -36,14 +38,19 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.ARP;
+import org.onlab.packet.Ethernet;
+import org.onlab.packet.IPv4;
 import org.onlab.packet.Ip4Address;
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onlab.util.KryoNamespace;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
@@ -61,7 +68,12 @@
 import org.onosproject.net.driver.DriverHandler;
 import org.onosproject.net.driver.DriverService;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.TrafficTreatment.Builder;
+import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.flow.instructions.ExtensionTreatment;
 import org.onosproject.net.flowobjective.Objective;
 import org.onosproject.net.group.DefaultGroupBucket;
@@ -75,6 +87,12 @@
 import org.onosproject.net.host.HostEvent;
 import org.onosproject.net.host.HostListener;
 import org.onosproject.net.host.HostService;
+import org.onosproject.net.packet.DefaultOutboundPacket;
+import org.onosproject.net.packet.InboundPacket;
+import org.onosproject.net.packet.OutboundPacket;
+import org.onosproject.net.packet.PacketContext;
+import org.onosproject.net.packet.PacketProcessor;
+import org.onosproject.net.packet.PacketService;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.onosproject.store.service.ConsistentMap;
 import org.onosproject.store.service.EventuallyConsistentMap;
@@ -96,13 +114,16 @@
 import org.onosproject.vtn.table.impl.L3ForwardServiceImpl;
 import org.onosproject.vtn.table.impl.SnatServiceImpl;
 import org.onosproject.vtn.util.DataPathIdGenerator;
+import org.onosproject.vtn.util.IpUtil;
 import org.onosproject.vtn.util.VtnConfig;
 import org.onosproject.vtn.util.VtnData;
 import org.onosproject.vtnrsc.AllowedAddressPair;
 import org.onosproject.vtnrsc.BindingHostId;
+import org.onosproject.vtnrsc.DefaultFloatingIp;
 import org.onosproject.vtnrsc.DefaultVirtualPort;
 import org.onosproject.vtnrsc.FixedIp;
 import org.onosproject.vtnrsc.FloatingIp;
+import org.onosproject.vtnrsc.FloatingIpId;
 import org.onosproject.vtnrsc.RouterId;
 import org.onosproject.vtnrsc.RouterInterface;
 import org.onosproject.vtnrsc.SecurityGroup;
@@ -183,6 +204,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected RouterInterfaceService routerInterfaceService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected FlowRuleService flowRuleService;
+
     private ApplicationId appId;
     private ClassifierService classifierService;
     private L2ForwardService l2ForwardService;
@@ -207,8 +231,12 @@
     private static final String EX_PORT_OF_DEVICE = "exPortOfDevice";
     private static final String EX_PORT_MAP = "exPortMap";
     private static final String DEFAULT_IP = "0.0.0.0";
+    private static final String FLOATINGSTORE = "vtn-floatingIp";
     private static final String USERDATA_IP = "169.254.169.254";
     private static final int SUBNET_NUM = 2;
+    private static final int SNAT_TABLE = 40;
+    private static final int SNAT_DEFAULT_RULE_PRIORITY = 0;
+    private static final byte[] ZERO_MAC_ADDRESS = MacAddress.ZERO.toBytes();
 
     private EventuallyConsistentMap<VirtualPortId, VirtualPort> vPortStore;
     private EventuallyConsistentMap<IpAddress, Boolean> switchesOfController;
@@ -216,8 +244,13 @@
     private EventuallyConsistentMap<SubnetId, Map<HostId, Host>> hostsOfSubnet;
     private EventuallyConsistentMap<TenantRouter, Boolean> routerInfFlagOfTenantRouter;
     private EventuallyConsistentMap<DeviceId, Port> exPortOfDevice;
+    private EventuallyConsistentMap<IpAddress, FloatingIp> floatingIpStore;
     private static ConsistentMap<String, String> exPortMap;
 
+    private VtnL3PacketProcessor l3PacketProcessor = new VtnL3PacketProcessor();
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected PacketService packetService;
+
     @Activate
     public void activate() {
         appId = coreService.registerApplication(APP_ID);
@@ -238,18 +271,29 @@
                                 .register(TenantNetworkId.class)
                                 .register(Host.class)
                                 .register(TenantNetwork.class)
+                                .register(TenantNetworkId.class)
                                 .register(TenantId.class)
                                 .register(SubnetId.class)
                                 .register(VirtualPortId.class)
                                 .register(VirtualPort.State.class)
                                 .register(AllowedAddressPair.class)
                                 .register(FixedIp.class)
+                                .register(FloatingIp.class)
+                                .register(FloatingIpId.class)
+                                .register(FloatingIp.Status.class)
+                                .register(UUID.class)
+                                .register(DefaultFloatingIp.class)
                                 .register(BindingHostId.class)
                                 .register(SecurityGroup.class)
                                 .register(IpAddress.class)
                                 .register(DefaultVirtualPort.class)
                                 .register(RouterId.class)
                                 .register(TenantRouter.class);
+        floatingIpStore = storageService
+                .<IpAddress, FloatingIp>eventuallyConsistentMapBuilder()
+                .withName(FLOATINGSTORE).withSerializer(serializer)
+                .withTimestampProvider((k, v) -> clockService.getTimestamp())
+                .build();
 
         vPortStore = storageService
                 .<VirtualPortId, VirtualPort>eventuallyConsistentMapBuilder()
@@ -295,6 +339,7 @@
                 .withSerializer(Serializer.using(Arrays.asList(KryoNamespaces.API)))
                 .build();
 
+        packetService.addProcessor(l3PacketProcessor, PacketProcessor.director(0));
         log.info("Started");
     }
 
@@ -464,6 +509,9 @@
             // Save external port
             Port export = getExPort(device.id());
             if (export != null) {
+                classifierService.programExportPortArpClassifierRules(export,
+                                                                      device.id(),
+                                                                      type);
                 exPortOfDevice.put(device.id(), export);
             }
             switchOfLocalHostPorts.put(device.id(), new NetworkOfLocalHostPorts());
@@ -867,11 +915,14 @@
 
     @Override
     public void onFloatingIpDetected(VtnRscEventFeedback l3Feedback) {
+        floatingIpStore.put(l3Feedback.floatingIp().floatingIp(),
+                            l3Feedback.floatingIp());
         programFloatingIpEvent(l3Feedback, VtnRscEvent.Type.FLOATINGIP_BIND);
     }
 
     @Override
     public void onFloatingIpVanished(VtnRscEventFeedback l3Feedback) {
+        floatingIpStore.remove(l3Feedback.floatingIp().floatingIp());
         programFloatingIpEvent(l3Feedback, VtnRscEvent.Type.FLOATINGIP_UNBIND);
     }
 
@@ -1100,8 +1151,6 @@
         List gwIpMac = getGwIpAndMac(vmPort);
         IpAddress dstVmGwIp = (IpAddress) gwIpMac.get(0);
         MacAddress dstVmGwMac = (MacAddress) gwIpMac.get(1);
-        List fGwIpMac = getGwIpAndMac(fipPort);
-        MacAddress fGwMac = (MacAddress) fGwIpMac.get(1);
         TenantNetwork vmNetwork = tenantNetworkService
                 .getNetwork(vmPort.networkId());
         TenantNetwork fipNetwork = tenantNetworkService
@@ -1109,26 +1158,26 @@
         // L3 downlink traffic flow
         MacAddress exPortMac = MacAddress.valueOf(exPort.annotations()
                                                   .value(AnnotationKeys.PORT_MAC));
-        classifierService.programArpClassifierRules(deviceId, floatingIp.floatingIp(),
-                                                    fipNetwork.segmentationId(),
-                                                    operation);
         classifierService.programL3ExPortClassifierRules(deviceId, exPort.number(),
                                                          floatingIp.floatingIp(), operation);
-        DriverHandler handler = driverService.createHandler(deviceId);
-        arpService.programArpRules(handler, deviceId, floatingIp.floatingIp(),
-                                         fipNetwork.segmentationId(), exPortMac,
-                                         operation);
         dnatService.programRules(deviceId, floatingIp.floatingIp(),
-                                     fGwMac, floatingIp.fixedIp(),
+                                 exPortMac, floatingIp.fixedIp(),
                                      l3vni, operation);
 
+        Subnet subnet = getSubnetOfFloatingIP(floatingIp);
+        IpPrefix ipPrefix = subnet.cidr();
+        snatService.programSnatSameSegmentUploadControllerRules(deviceId, l3vni,
+                                                                floatingIp.fixedIp(),
+                                                                floatingIp.floatingIp(),
+                                                                ipPrefix,
+                                                                operation);
         // L3 uplink traffic flow
         if (operation == Objective.Operation.ADD) {
             sendNorthSouthL3Flows(deviceId, floatingIp, dstVmGwIp, dstVmGwMac,
                                   l3vni, vmNetwork, vmPort, host, operation);
-            l2ForwardService.programLocalOut(deviceId,
-                                             fipNetwork.segmentationId(),
-                                             exPort.number(), fGwMac, operation);
+            l2ForwardService
+                    .programExternalOut(deviceId, fipNetwork.segmentationId(),
+                                        exPort.number(), exPortMac, operation);
         } else if (operation == Objective.Operation.REMOVE) {
             if (hostFlag || (!hostFlag
                     && routerInfFlagOfTenantRouter.get(tenantRouter) == null)) {
@@ -1147,15 +1196,13 @@
                 }
             }
             if (exPortFlag) {
-                l2ForwardService.programLocalOut(deviceId,
-                                                 fipNetwork.segmentationId(),
-                                                 exPort.number(), fGwMac, operation);
+                l2ForwardService.programExternalOut(deviceId,
+                                                    fipNetwork.segmentationId(),
+                                                    exPort.number(), exPortMac,
+                                                    operation);
             }
+            removeRulesInSnat(deviceId, floatingIp.fixedIp());
         }
-        snatService.programRules(deviceId, l3vni, floatingIp.fixedIp(),
-                                     fGwMac, exPortMac,
-                                     floatingIp.floatingIp(),
-                                     fipNetwork.segmentationId(), operation);
     }
 
     private Port getExPort(DeviceId deviceId) {
@@ -1280,4 +1327,306 @@
     public static void setExPortName(String name) {
         exPortMap.put(EX_PORT_KEY, name);
     }
+
+    /**
+     * Packet processor responsible for forwarding packets along their paths.
+     */
+    private class VtnL3PacketProcessor implements PacketProcessor {
+
+        @Override
+        public void process(PacketContext context) {
+            InboundPacket pkt = context.inPacket();
+            ConnectPoint connectPoint = pkt.receivedFrom();
+            DeviceId deviceId = connectPoint.deviceId();
+            Ethernet ethPkt = pkt.parsed();
+            if (ethPkt == null) {
+                return;
+            }
+            if (ethPkt.getEtherType() == Ethernet.TYPE_ARP) {
+                ARP arpPacket = (ARP) ethPkt.getPayload();
+                if ((arpPacket.getOpCode() == ARP.OP_REQUEST)) {
+                    arprequestProcess(arpPacket, deviceId);
+                } else if (arpPacket.getOpCode() == ARP.OP_REPLY) {
+                    arpresponceProcess(arpPacket, deviceId);
+                }
+            } else if (ethPkt.getEtherType() == Ethernet.TYPE_IPV4) {
+                if (ethPkt.getDestinationMAC().isMulticast()) {
+                    return;
+                }
+                IPv4 ip = (IPv4) ethPkt.getPayload();
+                upStreamPacketProcessor(ip, deviceId);
+
+            } else {
+                return;
+            }
+        }
+
+        private void arprequestProcess(ARP arpPacket, DeviceId deviceId) {
+            MacAddress dstMac = MacAddress
+                    .valueOf(arpPacket.getSenderHardwareAddress());
+            IpAddress srcIp = IpAddress.valueOf(IPv4
+                    .toIPv4Address(arpPacket.getTargetProtocolAddress()));
+            IpAddress dstIp = IpAddress.valueOf(IPv4
+                    .toIPv4Address(arpPacket.getSenderProtocolAddress()));
+            FloatingIp floatingIp = floatingIpStore.get(srcIp);
+            if (floatingIp == null) {
+                return;
+            }
+            DeviceId deviceIdOfFloatingIp = getDeviceIdOfFloatingIP(floatingIp);
+            if (!deviceId.equals(deviceIdOfFloatingIp)) {
+                return;
+            }
+            Port exPort = exPortOfDevice.get(deviceId);
+            MacAddress srcMac = MacAddress.valueOf(exPort.annotations()
+                    .value(AnnotationKeys.PORT_MAC));
+            if (!downloadSnatRules(deviceId, srcMac, srcIp, dstMac, dstIp,
+                                   floatingIp)) {
+                return;
+            }
+            Ethernet ethernet = buildArpResponse(dstIp, dstMac, srcIp, srcMac);
+            if (ethernet != null) {
+                sendPacketOut(deviceId, exPort.number(), ethernet);
+            }
+        }
+
+        private void arpresponceProcess(ARP arpPacket, DeviceId deviceId) {
+            MacAddress srcMac = MacAddress
+                    .valueOf(arpPacket.getTargetHardwareAddress());
+            MacAddress dstMac = MacAddress
+                    .valueOf(arpPacket.getSenderHardwareAddress());
+            IpAddress srcIp = IpAddress.valueOf(IPv4
+                    .toIPv4Address(arpPacket.getTargetProtocolAddress()));
+            IpAddress dstIp = IpAddress.valueOf(IPv4
+                    .toIPv4Address(arpPacket.getSenderProtocolAddress()));
+            FloatingIp floatingIp = floatingIpStore.get(srcIp);
+            if (floatingIp == null) {
+                return;
+            }
+            DeviceId deviceIdOfFloatingIp = getDeviceIdOfFloatingIP(floatingIp);
+            if (!deviceId.equals(deviceIdOfFloatingIp)) {
+                return;
+            }
+            if (!downloadSnatRules(deviceId, srcMac, srcIp, dstMac, dstIp,
+                                   floatingIp)) {
+                return;
+            }
+        }
+
+        private void upStreamPacketProcessor(IPv4 ipPacket, DeviceId deviceId) {
+            IpAddress srcIp = IpAddress.valueOf(ipPacket.getSourceAddress());
+            IpAddress dstIp = IpAddress.valueOf(ipPacket.getDestinationAddress());
+            FloatingIp floatingIp = null;
+            Collection<FloatingIp> floatingIps = floatingIpService
+                    .getFloatingIps();
+            Set<FloatingIp> floatingIpSet = Sets.newHashSet(floatingIps)
+                    .stream().collect(Collectors.toSet());
+            for (FloatingIp f : floatingIpSet) {
+                IpAddress fixIp = f.fixedIp();
+                if (fixIp != null && fixIp.equals(srcIp)) {
+                    floatingIp = f;
+                    break;
+                }
+            }
+            if (floatingIp == null) {
+                return;
+            }
+            Subnet subnet = getSubnetOfFloatingIP(floatingIp);
+            IpAddress gwIp = subnet.gatewayIp();
+            Port exportPort = exPortOfDevice.get(deviceId);
+            MacAddress exPortMac = MacAddress.valueOf(exportPort.annotations()
+                    .value(AnnotationKeys.PORT_MAC));
+            IpPrefix ipPrefix = subnet.cidr();
+            if (ipPrefix == null) {
+                return;
+            }
+            int mask = ipPrefix.prefixLength();
+            if (mask <= 0) {
+                return;
+            }
+            Ethernet ethernet = null;
+            // if the same ip segment
+            if (IpUtil.checkSameSegment(floatingIp.floatingIp(), dstIp, mask)) {
+                ethernet = buildArpRequest(dstIp, floatingIp.floatingIp(),
+                                           exPortMac);
+            } else {
+                ethernet = buildArpRequest(gwIp, floatingIp.floatingIp(),
+                                           exPortMac);
+            }
+            if (ethernet != null) {
+                sendPacketOut(deviceId, exportPort.number(), ethernet);
+            }
+        }
+    }
+
+    private Ethernet buildArpRequest(IpAddress targetIp, IpAddress sourceIp,
+                                     MacAddress sourceMac) {
+        ARP arp = new ARP();
+        arp.setHardwareType(ARP.HW_TYPE_ETHERNET)
+           .setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
+           .setProtocolType(ARP.PROTO_TYPE_IP)
+           .setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH)
+           .setOpCode(ARP.OP_REQUEST);
+
+        arp.setSenderHardwareAddress(sourceMac.toBytes())
+           .setSenderProtocolAddress(sourceIp.getIp4Address().toInt())
+           .setTargetHardwareAddress(ZERO_MAC_ADDRESS)
+           .setTargetProtocolAddress(targetIp.getIp4Address().toInt());
+
+        Ethernet ethernet = new Ethernet();
+        ethernet.setEtherType(Ethernet.TYPE_ARP)
+                .setDestinationMACAddress(MacAddress.BROADCAST)
+                .setSourceMACAddress(sourceMac)
+                .setPayload(arp);
+
+        ethernet.setPad(true);
+        return ethernet;
+    }
+
+    private Ethernet buildArpResponse(IpAddress targetIp, MacAddress targetMac,
+                                      IpAddress sourceIp, MacAddress sourceMac) {
+        ARP arp = new ARP();
+        arp.setHardwareType(ARP.HW_TYPE_ETHERNET)
+           .setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
+           .setProtocolType(ARP.PROTO_TYPE_IP)
+           .setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH)
+           .setOpCode(ARP.OP_REPLY);
+
+        arp.setSenderHardwareAddress(sourceMac.toBytes())
+           .setSenderProtocolAddress(sourceIp.getIp4Address().toInt())
+           .setTargetHardwareAddress(targetMac.toBytes())
+           .setTargetProtocolAddress(targetIp.getIp4Address().toInt());
+
+        Ethernet ethernet = new Ethernet();
+        ethernet.setEtherType(Ethernet.TYPE_ARP)
+                .setDestinationMACAddress(targetMac)
+                .setSourceMACAddress(sourceMac)
+                .setPayload(arp);
+
+        ethernet.setPad(true);
+
+        return ethernet;
+    }
+
+    private void sendPacketOut(DeviceId deviceId, PortNumber portNumber,
+                               Ethernet payload) {
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(portNumber).build();
+        OutboundPacket packet = new DefaultOutboundPacket(deviceId, treatment,
+                                                          ByteBuffer
+                                                                  .wrap(payload
+                                                                          .serialize()));
+        packetService.emit(packet);
+    }
+
+    private Subnet getSubnetOfFloatingIP(FloatingIp floatingIp) {
+        DeviceId exVmPortId = DeviceId
+                .deviceId(floatingIp.id().floatingIpId().toString());
+        Collection<VirtualPort> exVmPortList = virtualPortService
+                .getPorts(exVmPortId);
+        VirtualPort exVmPort = null;
+        if (exVmPortList != null) {
+            exVmPort = exVmPortList.iterator().next();
+        }
+        if (exVmPort == null) {
+            return null;
+        }
+        Set<FixedIp> fixedIps = exVmPort.fixedIps();
+        SubnetId subnetId = null;
+        for (FixedIp f : fixedIps) {
+            IpAddress fp = f.ip();
+            if (fp.equals(floatingIp.floatingIp())) {
+                subnetId = f.subnetId();
+                break;
+            }
+        }
+        if (subnetId == null) {
+            return null;
+        }
+        Subnet subnet = subnetService.getSubnet(subnetId);
+        return subnet;
+    }
+
+    private DeviceId getDeviceIdOfFloatingIP(FloatingIp floatingIp) {
+        VirtualPortId vmPortId = floatingIp.portId();
+        VirtualPort vmPort = virtualPortService.getPort(vmPortId);
+        if (vmPort == null) {
+            vmPort = VtnData.getPort(vPortStore, vmPortId);
+        }
+        Set<Host> hostSet = hostService.getHostsByMac(vmPort.macAddress());
+        Host host = null;
+        for (Host h : hostSet) {
+            String ifaceid = h.annotations().value(IFACEID);
+            if (ifaceid != null && ifaceid.equals(vmPortId.portId())) {
+                host = h;
+                break;
+            }
+        }
+        if (host == null) {
+            return null;
+        } else {
+            return host.location().deviceId();
+        }
+    }
+
+    private boolean downloadSnatRules(DeviceId deviceId, MacAddress srcMac,
+                                      IpAddress srcIp, MacAddress dstMac,
+                                      IpAddress dstIp, FloatingIp floatingIp) {
+        TenantNetwork exNetwork = tenantNetworkService
+                .getNetwork(floatingIp.networkId());
+        IpAddress fixedIp = floatingIp.fixedIp();
+        VirtualPortId vmPortId = floatingIp.portId();
+        VirtualPort vmPort = virtualPortService.getPort(vmPortId);
+        if (vmPort == null) {
+            vmPort = VtnData.getPort(vPortStore, vmPortId);
+        }
+        Subnet subnet = getSubnetOfFloatingIP(floatingIp);
+        IpPrefix ipPrefix = subnet.cidr();
+        IpAddress gwIp = subnet.gatewayIp();
+        if (ipPrefix == null) {
+            return false;
+        }
+        int mask = ipPrefix.prefixLength();
+        if (mask <= 0) {
+            return false;
+        }
+        TenantRouter tenantRouter = TenantRouter
+                .tenantRouter(floatingIp.tenantId(), floatingIp.routerId());
+        SegmentationId l3vni = vtnRscService.getL3vni(tenantRouter);
+        // if the same ip segment
+        if (IpUtil.checkSameSegment(srcIp, dstIp, mask)) {
+            snatService.programSnatSameSegmentRules(deviceId, l3vni, fixedIp,
+                                                    dstIp, dstMac, srcMac,
+                                                    srcIp,
+                                                    exNetwork.segmentationId(),
+                                                    Objective.Operation.ADD);
+            if (dstIp.equals(gwIp)) {
+                snatService
+                        .programSnatDiffSegmentRules(deviceId, l3vni, fixedIp,
+                                                     dstMac, srcMac, srcIp,
+                                                     exNetwork.segmentationId(),
+                                                     Objective.Operation.ADD);
+            }
+        }
+        return true;
+    }
+
+    private void removeRulesInSnat(DeviceId deviceId, IpAddress fixedIp) {
+        for (FlowEntry f : flowRuleService.getFlowEntries(deviceId)) {
+            if (f.tableId() == SNAT_TABLE
+                    && f.priority() > SNAT_DEFAULT_RULE_PRIORITY) {
+                String srcIp = f.selector()
+                        .getCriterion(Criterion.Type.IPV4_SRC).toString();
+                int priority = f.priority();
+                if (srcIp != null && srcIp.contains(fixedIp.toString())) {
+                    log.info("Match snat rules bob");
+                    TrafficSelector selector = f.selector();
+                    TrafficTreatment treatment = f.treatment();
+                    snatService.removeSnatRules(deviceId, selector, treatment,
+                                                priority,
+                                                Objective.Operation.REMOVE);
+
+                }
+            }
+        }
+    }
 }
diff --git a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/ClassifierService.java b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/ClassifierService.java
index 75df9b0..dc04a9a 100644
--- a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/ClassifierService.java
+++ b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/ClassifierService.java
@@ -20,8 +20,10 @@
 import org.onlab.packet.MacAddress;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.Objective.Operation;
 import org.onosproject.vtnrsc.SegmentationId;
 
 /**
@@ -134,4 +136,16 @@
                                         IpAddress dstIp, MacAddress dstmac,
                                         SegmentationId actionVni,
                                         Objective.Operation type);
+
+    /**
+     * Assemble the export port Arp Classifier table rules.
+     * Match: export port.
+     * Action: upload packet to controller.
+     *
+     * @param exportPort export port of ovs
+     * @param deviceId Device Id
+     * @param type the operation type of the flow rules
+     */
+    void programExportPortArpClassifierRules(Port exportPort, DeviceId deviceId,
+                                             Operation type);
 }
diff --git a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/L2ForwardService.java b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/L2ForwardService.java
index d313995..577bab3 100644
--- a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/L2ForwardService.java
+++ b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/L2ForwardService.java
@@ -79,6 +79,21 @@
                          Objective.Operation type);
 
     /**
+     * The external out rule that message matches Table(50).
+     * Match: external port mac and vnid.
+     * Action: output external port.
+     *
+     * @param deviceId Device Id
+     * @param segmentationId the vnid of the host belong to
+     * @param outPort the ingress port of the external port
+     * @param sourceMac the mac of the external port
+     * @param type the operation of the flow
+     */
+    void programExternalOut(DeviceId deviceId, SegmentationId segmentationId,
+                         PortNumber outPort, MacAddress sourceMac,
+                         Objective.Operation type);
+
+    /**
      * The tunnel out rule that message matches Table(50).
      * Match: host mac and vnid.
      * Action: output tunnel port.
diff --git a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/SnatService.java b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/SnatService.java
index 7e2939b..dd6f811 100644
--- a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/SnatService.java
+++ b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/SnatService.java
@@ -16,8 +16,11 @@
 package org.onosproject.vtn.table;
 
 import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
 import org.onlab.packet.MacAddress;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flowobjective.Objective;
 import org.onosproject.vtnrsc.SegmentationId;
 
@@ -30,6 +33,25 @@
 
     /**
      * Assemble the SNAT table rules.
+     * Match: ipv4 type, vnid, destination ip and source ip.
+     * Action: set eth_src, set eth_dst, set ip_src, set vnid and goto L2Forward Table(50).
+     *
+     * @param deviceId Device Id
+     * @param matchVni the vni of L3 network
+     * @param srcIP source ip
+     * @param dstIP destination ip
+     * @param ethDst external gateway mac
+     * @param ethSrc external port mac
+     * @param ipSrc floating ip
+     * @param actionVni external network VNI
+     * @param type the operation type of the flow rules
+     */
+    void programSnatSameSegmentRules(DeviceId deviceId, SegmentationId matchVni,
+                          IpAddress srcIP, IpAddress dstIP, MacAddress ethDst,
+                          MacAddress ethSrc, IpAddress ipSrc,
+                          SegmentationId actionVni, Objective.Operation type);
+    /**
+     * Assemble the SNAT table rules.
      * Match: ipv4 type, vnid and source ip.
      * Action: set eth_src, set eth_dst, set ip_src, set vnid and goto L2Forward Table(50).
      *
@@ -42,8 +64,39 @@
      * @param actionVni external network VNI
      * @param type the operation type of the flow rules
      */
-    void programRules(DeviceId deviceId, SegmentationId matchVni,
+    void programSnatDiffSegmentRules(DeviceId deviceId, SegmentationId matchVni,
                           IpAddress srcIP, MacAddress ethDst,
                           MacAddress ethSrc, IpAddress ipSrc,
                           SegmentationId actionVni, Objective.Operation type);
+
+    /**
+     * Assemble the SNAT table rules.
+     * Match: ipv4 type, vnid, destination ip and source ip.
+     * Action: upload to controller.
+     *
+     * @param deviceId Device Id
+     * @param matchVni the vni of L3 network
+     * @param srcIP source ip
+     * @param dstIP destination ip
+     * @param type the operation type of the flow rules
+     */
+    void programSnatSameSegmentUploadControllerRules(DeviceId deviceId,
+                                                     SegmentationId matchVni,
+                                                     IpAddress srcIP,
+                                                     IpAddress dstIP,
+                                                     IpPrefix prefix,
+                                                     Objective.Operation type);
+
+    /**
+     * Remove the SNAT table rules.
+     *
+     * @param deviceId Device Id
+     * @param selector selector of rules
+     * @param treatment treatment of rules
+     * @param priority priority of rules
+     * @param type the operation type of the flow rules
+     */
+    void removeSnatRules(DeviceId deviceId, TrafficSelector selector,
+                         TrafficTreatment treatment, int priority,
+                         Objective.Operation type);
 }
diff --git a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/impl/ClassifierServiceImpl.java b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/impl/ClassifierServiceImpl.java
index c41aafe..17471f6 100644
--- a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/impl/ClassifierServiceImpl.java
+++ b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/impl/ClassifierServiceImpl.java
@@ -28,6 +28,7 @@
 import org.onlab.packet.MacAddress;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
@@ -40,6 +41,7 @@
 import org.onosproject.net.flowobjective.ForwardingObjective;
 import org.onosproject.net.flowobjective.ForwardingObjective.Flag;
 import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.Objective.Operation;
 import org.onosproject.vtn.table.ClassifierService;
 import org.onosproject.vtnrsc.SegmentationId;
 import org.slf4j.Logger;
@@ -242,4 +244,24 @@
             flowObjectiveService.forward(deviceId, objective.remove());
         }
     }
+
+    @Override
+    public void programExportPortArpClassifierRules(Port exportPort,
+                                                    DeviceId deviceId,
+                                                    Operation type) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(EtherType.ARP.ethType().toShort())
+                .matchInPort(exportPort.number()).build();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        treatment.add(Instructions.createOutput(PortNumber.CONTROLLER));
+        ForwardingObjective.Builder objective = DefaultForwardingObjective
+                .builder().withTreatment(treatment.build())
+                .withSelector(selector).fromApp(appId).withFlag(Flag.SPECIFIC)
+                .withPriority(L3_CLASSIFIER_PRIORITY);
+        if (type.equals(Objective.Operation.ADD)) {
+            flowObjectiveService.forward(deviceId, objective.add());
+        } else {
+            flowObjectiveService.forward(deviceId, objective.remove());
+        }
+    }
 }
diff --git a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/impl/L2ForwardServiceImpl.java b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/impl/L2ForwardServiceImpl.java
index c19a38a..a91b29e 100644
--- a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/impl/L2ForwardServiceImpl.java
+++ b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/impl/L2ForwardServiceImpl.java
@@ -176,6 +176,28 @@
     }
 
     @Override
+    public void programExternalOut(DeviceId deviceId,
+                                SegmentationId segmentationId,
+                                PortNumber outPort, MacAddress sourceMac,
+                                Objective.Operation type) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchTunnelId(Long.parseLong(segmentationId.toString()))
+                .matchEthSrc(sourceMac).build();
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setOutput(outPort).build();
+        ForwardingObjective.Builder objective = DefaultForwardingObjective
+                .builder().withTreatment(treatment).withSelector(selector)
+                .fromApp(appId).withFlag(Flag.SPECIFIC)
+                .withPriority(MAC_PRIORITY);
+        if (type.equals(Objective.Operation.ADD)) {
+            flowObjectiveService.forward(deviceId, objective.add());
+        } else {
+            flowObjectiveService.forward(deviceId, objective.remove());
+        }
+
+    }
+
+    @Override
     public void programTunnelOut(DeviceId deviceId,
                                  SegmentationId segmentationId,
                                  PortNumber tunnelOutPort, MacAddress dstMac,
diff --git a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/impl/SnatServiceImpl.java b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/impl/SnatServiceImpl.java
index 40cd350..cd28ed8 100644
--- a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/impl/SnatServiceImpl.java
+++ b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/table/impl/SnatServiceImpl.java
@@ -16,7 +16,6 @@
 package org.onosproject.vtn.table.impl;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static org.slf4j.LoggerFactory.getLogger;
 
 import org.onlab.osgi.DefaultServiceDirectory;
 import org.onlab.osgi.ServiceDirectory;
@@ -26,26 +25,29 @@
 import org.onlab.packet.MacAddress;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.instructions.Instructions;
 import org.onosproject.net.flowobjective.DefaultForwardingObjective;
 import org.onosproject.net.flowobjective.FlowObjectiveService;
 import org.onosproject.net.flowobjective.ForwardingObjective;
 import org.onosproject.net.flowobjective.ForwardingObjective.Flag;
 import org.onosproject.net.flowobjective.Objective;
+import org.onosproject.net.flowobjective.Objective.Operation;
 import org.onosproject.vtn.table.SnatService;
 import org.onosproject.vtnrsc.SegmentationId;
-import org.slf4j.Logger;
 
 /**
  * Provides implementation of SnatService.
  */
 public class SnatServiceImpl implements SnatService {
-    private final Logger log = getLogger(getClass());
 
-    private static final int SNAT_PRIORITY = 0xffff;
+    private static final int SNAT_SAME_SEG_PRIORITY = 0xffff;
+    private static final int SNAT_SAME_SEG_CON_PRIORITY = 0xfff0;
+    private static final int SNAT_DIFF_SEG_PRIORITY = 0xffe0;
     private static final int PREFIC_LENGTH = 32;
 
     private final FlowObjectiveService flowObjectiveService;
@@ -63,7 +65,32 @@
     }
 
     @Override
-    public void programRules(DeviceId deviceId, SegmentationId matchVni,
+    public void programSnatSameSegmentRules(DeviceId deviceId, SegmentationId matchVni,
+                             IpAddress srcIP, IpAddress dstIP, MacAddress ethDst,
+                             MacAddress ethSrc, IpAddress ipSrc,
+                             SegmentationId actionVni, Objective.Operation type) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(Long.parseLong(matchVni.segmentationId()))
+                .matchIPSrc(IpPrefix.valueOf(srcIP, PREFIC_LENGTH))
+                .matchIPDst(IpPrefix.valueOf(dstIP, PREFIC_LENGTH)).build();
+
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        treatment.setEthDst(ethDst).setEthSrc(ethSrc).setIpSrc(ipSrc)
+                .setTunnelId(Long.parseLong(actionVni.segmentationId()));
+        ForwardingObjective.Builder objective = DefaultForwardingObjective
+                .builder().withTreatment(treatment.build())
+                .withSelector(selector).fromApp(appId).withFlag(Flag.SPECIFIC)
+                .withPriority(SNAT_SAME_SEG_PRIORITY);
+        if (type.equals(Objective.Operation.ADD)) {
+            flowObjectiveService.forward(deviceId, objective.add());
+        } else {
+            flowObjectiveService.forward(deviceId, objective.remove());
+        }
+    }
+
+    @Override
+    public void programSnatDiffSegmentRules(DeviceId deviceId, SegmentationId matchVni,
                              IpAddress srcIP, MacAddress ethDst,
                              MacAddress ethSrc, IpAddress ipSrc,
                              SegmentationId actionVni, Objective.Operation type) {
@@ -78,12 +105,51 @@
         ForwardingObjective.Builder objective = DefaultForwardingObjective
                 .builder().withTreatment(treatment.build())
                 .withSelector(selector).fromApp(appId).withFlag(Flag.SPECIFIC)
-                .withPriority(SNAT_PRIORITY);
+                .withPriority(SNAT_DIFF_SEG_PRIORITY);
         if (type.equals(Objective.Operation.ADD)) {
-            log.debug("RouteRules-->ADD");
             flowObjectiveService.forward(deviceId, objective.add());
         } else {
-            log.debug("RouteRules-->REMOVE");
+            flowObjectiveService.forward(deviceId, objective.remove());
+        }
+    }
+
+    @Override
+    public void programSnatSameSegmentUploadControllerRules(DeviceId deviceId,
+                                                            SegmentationId matchVni,
+                                                            IpAddress srcIP,
+                                                            IpAddress dstIP,
+                                                            IpPrefix prefix,
+                                                            Operation type) {
+
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchTunnelId(Long.parseLong(matchVni.segmentationId()))
+                .matchIPSrc(IpPrefix.valueOf(srcIP, PREFIC_LENGTH))
+                .matchIPDst(IpPrefix.valueOf(dstIP, prefix.prefixLength()))
+                .build();
+        TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
+        treatment.add(Instructions.createOutput(PortNumber.CONTROLLER));
+        ForwardingObjective.Builder objective = DefaultForwardingObjective
+                .builder().withTreatment(treatment.build())
+                .withSelector(selector).fromApp(appId).withFlag(Flag.SPECIFIC)
+                .withPriority(SNAT_SAME_SEG_CON_PRIORITY);
+        if (type.equals(Objective.Operation.ADD)) {
+            flowObjectiveService.forward(deviceId, objective.add());
+        } else {
+            flowObjectiveService.forward(deviceId, objective.remove());
+        }
+    }
+
+    @Override
+    public void removeSnatRules(DeviceId deviceId, TrafficSelector selector,
+                                TrafficTreatment treatment, int priority,
+                                Objective.Operation type) {
+        ForwardingObjective.Builder objective = DefaultForwardingObjective
+                .builder().withTreatment(treatment).withSelector(selector)
+                .fromApp(appId).withFlag(Flag.SPECIFIC).withPriority(priority);
+        if (type.equals(Objective.Operation.ADD)) {
+            flowObjectiveService.forward(deviceId, objective.add());
+        } else {
             flowObjectiveService.forward(deviceId, objective.remove());
         }
     }
diff --git a/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/util/IpUtil.java b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/util/IpUtil.java
new file mode 100644
index 0000000..82865c5
--- /dev/null
+++ b/apps/vtn/vtnmgr/src/main/java/org/onosproject/vtn/util/IpUtil.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.vtn.util;
+
+import org.onlab.packet.IpAddress;
+
+/**
+ * IpUtil utility class.
+ */
+public final class IpUtil {
+
+    private IpUtil() {
+    }
+
+    /**
+     * check source Ip and destination Ip in same Subnet.
+     *
+     * @param srcIp source Ip
+     * @param dstIp destination
+     * @param masks netmask length
+     * @return boolean
+     */
+    public static boolean checkSameSegment(IpAddress srcIp, IpAddress dstIp,
+                                           int mask) {
+        String[] ips = srcIp.toString().split("\\.");
+        int ipAddr = (Integer.parseInt(ips[0]) << 24)
+                | (Integer.parseInt(ips[1]) << 16)
+                | (Integer.parseInt(ips[2]) << 8)
+                | Integer.parseInt(ips[3]);
+        int netmask = 0xFFFFFFFF << (32 - mask);
+        String[] cidrIps = dstIp.toString().split("\\.");
+        int cidrIpAddr = (Integer.parseInt(cidrIps[0]) << 24)
+                | (Integer.parseInt(cidrIps[1]) << 16)
+                | (Integer.parseInt(cidrIps[2]) << 8)
+                | Integer.parseInt(cidrIps[3]);
+
+        return (ipAddr & netmask) == (cidrIpAddr & netmask);
+    }
+}