NETVIRT-1519: MIP entry duplicated in FIB
[netvirt.git] / vpnmanager / impl / src / main / java / org / opendaylight / netvirt / vpnmanager / iplearn / AbstractIpLearnNotificationHandler.java
1 /*
2  * Copyright (c) 2018 Alten Calsoft Labs India 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.vpnmanager.iplearn;
10
11 import com.google.common.base.Optional;
12 import com.google.common.cache.Cache;
13 import com.google.common.cache.CacheBuilder;
14 import java.math.BigInteger;
15 import java.util.List;
16 import java.util.Objects;
17 import java.util.concurrent.TimeUnit;
18 import org.apache.commons.lang3.tuple.ImmutablePair;
19 import org.apache.commons.lang3.tuple.Pair;
20 import org.opendaylight.genius.mdsalutil.NWUtil;
21 import org.opendaylight.netvirt.neutronvpn.api.enums.IpVersionChoice;
22 import org.opendaylight.netvirt.neutronvpn.interfaces.INeutronVpnManager;
23 import org.opendaylight.netvirt.vpnmanager.VpnUtil;
24 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
25 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpPrefix;
26 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpPrefixBuilder;
27 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
28 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.LearntVpnVipToPortEventAction;
29 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.adjacency.list.Adjacency;
30 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.learnt.vpn.vip.to.port.data.LearntVpnVipToPort;
31 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.neutron.vpn.portip.port.data.VpnPortipToPort;
32 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.vpn.config.rev161130.VpnConfig;
33 import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.ports.rev150712.ports.attributes.ports.Port;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 public abstract class AbstractIpLearnNotificationHandler {
38
39     private static final Logger LOG = LoggerFactory.getLogger(AbstractIpLearnNotificationHandler.class);
40
41     // temp where Key is VPNInstance+IP and value is timestamp
42     private final Cache<Pair<String, String>, BigInteger> migrateIpCache;
43
44     protected final VpnConfig config;
45     protected final VpnUtil vpnUtil;
46     protected final INeutronVpnManager neutronVpnManager;
47     private long bootupTime = 0L;
48
49     public AbstractIpLearnNotificationHandler(VpnConfig vpnConfig, VpnUtil vpnUtil,
50             INeutronVpnManager neutronVpnManager) {
51         this.config = vpnConfig;
52         this.vpnUtil = vpnUtil;
53         this.neutronVpnManager = neutronVpnManager;
54
55         long duration = config.getIpLearnTimeout() * 10;
56         long cacheSize = config.getMigrateIpCacheSize().longValue();
57         migrateIpCache =
58                 CacheBuilder.newBuilder().maximumSize(cacheSize).expireAfterWrite(duration,
59                         TimeUnit.MILLISECONDS).build();
60         this.bootupTime = System.currentTimeMillis();
61     }
62
63     protected void validateAndProcessIpLearning(String srcInterface, IpAddress srcIP, MacAddress srcMac,
64             IpAddress targetIP, BigInteger metadata) {
65         List<Adjacency> adjacencies = vpnUtil.getAdjacenciesForVpnInterfaceFromConfig(srcInterface);
66         IpVersionChoice srcIpVersion = vpnUtil.getIpVersionFromString(srcIP.stringValue());
67         boolean isSrcIpVersionPartOfVpn = false;
68         if (adjacencies != null && !adjacencies.isEmpty()) {
69             for (Adjacency adj : adjacencies) {
70                 IpPrefix ipPrefix = IpPrefixBuilder.getDefaultInstance(adj.getIpAddress());
71                 // If extra/static route is configured, we should ignore for learning process
72                 if (NWUtil.isIpAddressInRange(srcIP, ipPrefix)) {
73                     return;
74                 }
75                 IpVersionChoice currentAdjIpVersion = vpnUtil.getIpVersionFromString(adj.getIpAddress());
76                 if (srcIpVersion.isIpVersionChosen(currentAdjIpVersion)) {
77                     isSrcIpVersionPartOfVpn = true;
78                 }
79             }
80             //If srcIP version is not part of the srcInterface VPN Adjacency, ignore IpLearning process
81             if (!isSrcIpVersionPartOfVpn) {
82                 return;
83             }
84         }
85
86         LOG.trace("ARP/NA Notification Response Received from interface {} and IP {} having MAC {}, learning MAC",
87                 srcInterface, srcIP.stringValue(), srcMac.getValue());
88         processIpLearning(srcInterface, srcIP, srcMac, metadata, targetIP);
89     }
90
91     protected void processIpLearning(String srcInterface, IpAddress srcIP, MacAddress srcMac, BigInteger metadata,
92                                      IpAddress dstIP) {
93
94         if (!VpnUtil.isArpLearningEnabled()) {
95             LOG.trace("Not handling packet as ARP Based Learning is disabled");
96             return;
97         }
98         if (metadata == null || Objects.equals(metadata, BigInteger.ZERO)) {
99             return;
100         }
101
102         Optional<List<String>> vpnList = vpnUtil.getVpnHandlingIpv4AssociatedWithInterface(srcInterface);
103         if (!vpnList.isPresent()) {
104             LOG.info("IP LEARN NO_RESOLVE: VPN  not configured. Ignoring responding to ARP/NA requests from this"
105                     + " Interface {}.", srcInterface);
106             return;
107         }
108
109         String srcIpToQuery = srcIP.stringValue();
110         String destIpToQuery = dstIP.stringValue();
111         for (String vpnName : vpnList.get()) {
112             LOG.info("Received ARP/NA for sender MAC {} and sender IP {} via interface {}", srcMac.getValue(),
113                     srcIpToQuery, srcInterface);
114             synchronized ((vpnName + srcIpToQuery).intern()) {
115                 VpnPortipToPort vpnPortipToPort = vpnUtil.getNeutronPortFromVpnPortFixedIp(vpnName, srcIpToQuery);
116                 // Check if this IP belongs to  external network
117                 if (vpnPortipToPort == null) {
118                     String extSubnetId = vpnUtil.getAssociatedExternalSubnet(srcIpToQuery);
119                     if (extSubnetId != null) {
120                         vpnPortipToPort =
121                                 vpnUtil.getNeutronPortFromVpnPortFixedIp(extSubnetId, srcIpToQuery);
122                     }
123                 }
124                 if (vpnPortipToPort != null && !vpnPortipToPort.isLearntIp()) {
125                     /*
126                      * This is a well known neutron port and so should be ignored from being
127                      * discovered...unless it is an Octavia VIP
128                      */
129                     String portName = vpnPortipToPort.getPortName();
130                     Port neutronPort = neutronVpnManager.getNeutronPort(portName);
131
132                     if (neutronPort == null) {
133                         LOG.warn("{} should have been a neutron port but could not retrieve it. Aborting processing",
134                                 portName);
135                         continue;
136                     }
137
138                     if (!"Octavia".equals(neutronPort.getDeviceOwner())) {
139                         LOG.debug("Neutron port {} is not an Octavia port, ignoring", portName);
140                         continue;
141                     }
142                 }
143                 // For IPs learnt before cluster-reboot/upgrade, GARP/ArpResponse is received
144                 // within 300sec
145                 // after reboot, it would be ignored.
146                 if (vpnPortipToPort != null && vpnPortipToPort.isLearntIp()) {
147                     if (System.currentTimeMillis() < this.bootupTime + config.getBootDelayArpLearning() * 1000) {
148                         LOG.trace("GARP/Arp Response not handled for IP {} vpnName {} for time {}s",
149                                 vpnPortipToPort.getPortFixedip(), vpnName, config.getBootDelayArpLearning());
150                         continue;
151                     }
152                 }
153                 LearntVpnVipToPort learntVpnVipToPort = vpnUtil.getLearntVpnVipToPort(vpnName, srcIpToQuery);
154                 if (learntVpnVipToPort != null) {
155                     String oldPortName = learntVpnVipToPort.getPortName();
156                     String oldMac = learntVpnVipToPort.getMacAddress();
157                     if (!oldMac.equalsIgnoreCase(srcMac.getValue())) {
158                         // MAC has changed for requested IP
159                         LOG.info("ARP/NA Source IP/MAC data modified for IP {} with MAC {} and Port {}", srcIpToQuery,
160                                 srcMac, srcInterface);
161                         vpnUtil.createLearntVpnVipToPortEvent(vpnName, srcIpToQuery, destIpToQuery, oldPortName, oldMac,
162                                 LearntVpnVipToPortEventAction.Delete, null);
163                         putVpnIpToMigrateIpCache(vpnName, srcIpToQuery, srcMac);
164                     }
165                 } else if (!isIpInMigrateCache(vpnName, srcIpToQuery)) {
166                     if (vpnPortipToPort != null && !vpnPortipToPort.getPortName().equals(srcInterface)) {
167                         LOG.trace(
168                                 "LearntIp: {} vpnName {} is already present in VpnPortIpToPort with " + "PortName {} ",
169                                 srcIpToQuery, vpnName, vpnPortipToPort.getPortName());
170                         vpnUtil.createLearntVpnVipToPortEvent(vpnName, srcIpToQuery, destIpToQuery,
171                                 vpnPortipToPort.getPortName(), vpnPortipToPort.getMacAddress(),
172                                 LearntVpnVipToPortEventAction.Delete, null);
173                         continue;
174                     }
175                     learnMacFromIncomingPacket(vpnName, srcInterface, srcIP, srcMac, dstIP);
176                 }
177             }
178         }
179     }
180
181     private void learnMacFromIncomingPacket(String vpnName, String srcInterface, IpAddress srcIP, MacAddress srcMac,
182             IpAddress dstIP) {
183         String srcIpToQuery = srcIP.stringValue();
184         String destIpToQuery = dstIP.stringValue();
185         synchronized ((vpnName + srcIpToQuery).intern()) {
186             vpnUtil.createLearntVpnVipToPortEvent(vpnName, srcIpToQuery, destIpToQuery, srcInterface,
187                     srcMac.getValue(), LearntVpnVipToPortEventAction.Add, null);
188         }
189     }
190
191     private void putVpnIpToMigrateIpCache(String vpnName, String ipToQuery, MacAddress srcMac) {
192         long cacheSize = config.getMigrateIpCacheSize().longValue();
193         if (migrateIpCache.size() >= cacheSize) {
194             LOG.debug("IP_MIGRATE_CACHE: max size {} reached, assuming cache eviction we still put IP {}"
195                     + " vpnName {} with MAC {}", cacheSize, ipToQuery, vpnName, srcMac);
196         }
197         LOG.debug("IP_MIGRATE_CACHE: add to dirty cache IP {} vpnName {} with MAC {}", ipToQuery, vpnName, srcMac);
198         migrateIpCache.put(new ImmutablePair<>(vpnName, ipToQuery),
199                 new BigInteger(String.valueOf(System.currentTimeMillis())));
200     }
201
202     private boolean isIpInMigrateCache(String vpnName, String ipToQuery) {
203         if (migrateIpCache == null || migrateIpCache.size() == 0) {
204             return false;
205         }
206         Pair<String, String> keyPair = new ImmutablePair<>(vpnName, ipToQuery);
207         BigInteger prevTimeStampCached = migrateIpCache.getIfPresent(keyPair);
208         if (prevTimeStampCached == null) {
209             LOG.debug("IP_MIGRATE_CACHE: there is no IP {} vpnName {} in dirty cache, so learn it",
210                     ipToQuery, vpnName);
211             return false;
212         }
213         if (System.currentTimeMillis() > prevTimeStampCached.longValue() + config.getIpLearnTimeout()) {
214             LOG.debug("IP_MIGRATE_CACHE: older than timeout value - remove from dirty cache IP {} vpnName {}",
215                     ipToQuery, vpnName);
216             migrateIpCache.invalidate(keyPair);
217             return false;
218         }
219         LOG.debug("IP_MIGRATE_CACHE: younger than timeout value - ignore learning IP {} vpnName {}",
220                 ipToQuery, vpnName);
221         return true;
222     }
223 }