fe7f05cd4ee8e0cdf441841f99028a7544d9f4eb
[netvirt.git] / vpnservice / vpnmanager / vpnmanager-impl / src / main / java / org / opendaylight / netvirt / vpnmanager / ArpNotificationHandler.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 package org.opendaylight.netvirt.vpnmanager;
9
10 import com.google.common.base.Optional;
11 import com.google.common.cache.Cache;
12 import com.google.common.cache.CacheBuilder;
13 import java.math.BigInteger;
14 import java.util.ArrayList;
15 import java.util.Collections;
16 import java.util.List;
17 import java.util.Objects;
18 import java.util.concurrent.TimeUnit;
19
20 import org.apache.commons.lang3.tuple.ImmutablePair;
21 import org.apache.commons.lang3.tuple.Pair;
22 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
23 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
24 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
25 import org.opendaylight.genius.mdsalutil.MDSALUtil;
26 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
27 import org.opendaylight.yang.gen.v1.urn.huawei.params.xml.ns.yang.l3vpn.rev140815.VpnInterfaces;
28 import org.opendaylight.yang.gen.v1.urn.huawei.params.xml.ns.yang.l3vpn.rev140815.vpn.interfaces.VpnInterface;
29 import org.opendaylight.yang.gen.v1.urn.huawei.params.xml.ns.yang.l3vpn.rev140815.vpn.interfaces.VpnInterfaceBuilder;
30 import org.opendaylight.yang.gen.v1.urn.huawei.params.xml.ns.yang.l3vpn.rev140815.vpn.interfaces.VpnInterfaceKey;
31 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
32 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.PhysAddress;
33 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.arputil.rev160406.ArpRequestReceived;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.arputil.rev160406.ArpResponseReceived;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.arputil.rev160406.MacChanged;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.arputil.rev160406.OdlArputilListener;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.Adjacencies;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.adjacency.list.Adjacency;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.adjacency.list.AdjacencyBuilder;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.adjacency.list.AdjacencyKey;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.learnt.vpn.vip.to.port.data.LearntVpnVipToPort;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.vpn.id.to.vpn.instance.VpnIds;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.neutron.vpn.portip.port.data.VpnPortipToPort;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.vpn.config.rev161130.VpnConfig;
47 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 public class ArpNotificationHandler implements OdlArputilListener {
52     private static final Logger LOG = LoggerFactory.getLogger(ArpNotificationHandler.class);
53     // temp where Key is VPNInstance+IP and value is timestamp
54     private final Cache<Pair<String, String>, BigInteger> migrateArpCache;
55
56     DataBroker dataBroker;
57     IdManagerService idManager;
58     IInterfaceManager interfaceManager;
59     private final VpnConfig config;
60
61
62     public ArpNotificationHandler(DataBroker dataBroker, IdManagerService idManager,
63                                   IInterfaceManager interfaceManager, VpnConfig vpnConfig) {
64         this.dataBroker = dataBroker;
65         this.idManager = idManager;
66         this.interfaceManager = interfaceManager;
67         this.config = vpnConfig;
68
69         long duration = config.getArpLearnTimeout() * 10;
70         long cacheSize = config.getArpCacheSize().longValue();
71         migrateArpCache =
72                 CacheBuilder.newBuilder().maximumSize(cacheSize).expireAfterWrite(duration,
73                         TimeUnit.MILLISECONDS).build();
74     }
75
76     @Override
77     public void onMacChanged(MacChanged notification) {
78
79     }
80
81     @Override
82     public void onArpRequestReceived(ArpRequestReceived notification) {
83         String srcInterface = notification.getInterface();
84         IpAddress srcIP = notification.getSrcIpaddress();
85         PhysAddress srcMac = notification.getSrcMac();
86         IpAddress targetIP = notification.getDstIpaddress();
87         BigInteger metadata = notification.getMetadata();
88         boolean isGarp = srcIP.equals(targetIP);
89         if (!isGarp) {
90             LOG.trace("ArpNotification Non-Gratuitous Request Received from "
91                       + "interface {} and IP {} having MAC {} target destination {}, ignoring..",
92                     srcInterface, srcIP.getIpv4Address().getValue(),srcMac.getValue(),
93                     targetIP.getIpv4Address().getValue());
94             return;
95         }
96         LOG.trace("ArpNotification Gratuitous Request Received from "
97                   + "interface {} and IP {} having MAC {} target destination {}, learning MAC",
98                   srcInterface, srcIP.getIpv4Address().getValue(),srcMac.getValue(),
99                   targetIP.getIpv4Address().getValue());
100         processArpLearning(srcInterface, srcIP, srcMac, metadata, targetIP);
101     }
102
103     @Override
104     public void onArpResponseReceived(ArpResponseReceived notification) {
105         String srcInterface = notification.getInterface();
106         IpAddress srcIP = notification.getSrcIpaddress();
107         PhysAddress srcMac = notification.getSrcMac();
108         BigInteger metadata = notification.getMetadata();
109         IpAddress targetIP = notification.getDstIpaddress();
110         LOG.trace("ArpNotification Response Received from interface {} and IP {} having MAC {}, learning MAC",
111                 srcInterface, srcIP.getIpv4Address().getValue(), srcMac.getValue());
112         processArpLearning(srcInterface, srcIP, srcMac, metadata, targetIP);
113     }
114
115     private void processArpLearning(String srcInterface, IpAddress srcIP, PhysAddress srcMac, BigInteger metadata,
116             IpAddress dstIP) {
117         if (metadata != null && !Objects.equals(metadata, BigInteger.ZERO)) {
118             long vpnId = MetaDataUtil.getVpnIdFromMetadata(metadata);
119             // Process ARP only if vpnservice is configured on the interface
120             InstanceIdentifier<VpnIds> vpnIdsInstanceIdentifier = VpnUtil.getVpnIdToVpnInstanceIdentifier(vpnId);
121             Optional<VpnIds> vpnIdsOptional
122                     = VpnUtil.read(dataBroker, LogicalDatastoreType.CONFIGURATION, vpnIdsInstanceIdentifier);
123             if (!vpnIdsOptional.isPresent()) {
124                 LOG.trace("ARP NO_RESOLVE: VPN {} not configured. Ignoring responding to ARP requests on this VPN",
125                         vpnId);
126                 return;
127             }
128             VpnIds vpnIds = vpnIdsOptional.get();
129             String vpnName = vpnIds.getVpnInstanceName();
130             if (VpnUtil.isInterfaceAssociatedWithVpn(dataBroker, vpnName, srcInterface)) {
131                 LOG.debug("Received ARP for sender MAC {} and sender IP {} via interface {}",
132                           srcMac.getValue(), srcIP.getIpv4Address().getValue(), srcInterface);
133                 String ipToQuery = srcIP.getIpv4Address().getValue();
134                 LOG.trace("ARP being processed for Source IP {}", ipToQuery);
135                 VpnPortipToPort vpnPortipToPort =
136                         VpnUtil.getNeutronPortFromVpnPortFixedIp(dataBroker, vpnName, ipToQuery);
137                 if (vpnPortipToPort != null) {
138                     /* This is a well known neutron port and so should be ignored
139                      * from being discovered
140                      */
141                     return;
142                 }
143                 LearntVpnVipToPort learntVpnVipToPort = VpnUtil.getLearntVpnVipToPort(dataBroker, vpnName, ipToQuery);
144                 if (learntVpnVipToPort != null) {
145                     String oldPortName = learntVpnVipToPort.getPortName();
146                     String oldMac = learntVpnVipToPort.getMacAddress();
147                     if (!oldMac.equalsIgnoreCase(srcMac.getValue())) {
148                         //MAC has changed for requested IP
149                         LOG.trace("ARP Source IP/MAC data modified for IP {} with MAC {} and Port {}",
150                                 ipToQuery, srcMac, srcInterface);
151                         synchronized ((vpnName + ipToQuery).intern()) {
152                             removeMipAdjacency(vpnName, oldPortName, srcIP);
153                             VpnUtil.removeLearntVpnVipToPort(dataBroker, vpnName, ipToQuery);
154                             putVpnIpToMigrateArpCache(vpnName, ipToQuery, srcMac);
155                         }
156                     }
157                 } else if (!isIpInArpMigrateCache(vpnName, ipToQuery)) {
158                     learnMacFromArpPackets(vpnName, srcInterface, srcIP, srcMac, dstIP);
159                 }
160             }
161         }
162     }
163
164     private void learnMacFromArpPackets(String vpnName, String srcInterface,
165         IpAddress srcIP, PhysAddress srcMac, IpAddress dstIP) {
166         String ipToQuery = srcIP.getIpv4Address().getValue();
167         synchronized ((vpnName + ipToQuery).intern()) {
168             VpnUtil.createLearntVpnVipToPort(dataBroker, vpnName, ipToQuery, srcInterface, srcMac.getValue());
169             addMipAdjacency(vpnName, srcInterface, srcIP, srcMac.getValue(), dstIP);
170         }
171     }
172
173     private void addMipAdjacency(String vpnName, String vpnInterface, IpAddress srcPrefix, String mipMacAddress,
174             IpAddress dstPrefix) {
175         LOG.trace("Adding {} adjacency to VPN Interface {} ",srcPrefix,vpnInterface);
176         InstanceIdentifier<VpnInterface> vpnIfId = VpnUtil.getVpnInterfaceIdentifier(vpnInterface);
177         InstanceIdentifier<Adjacencies> path = vpnIfId.augmentation(Adjacencies.class);
178         synchronized (vpnInterface.intern()) {
179             Optional<Adjacencies> adjacencies = VpnUtil.read(dataBroker, LogicalDatastoreType.CONFIGURATION, path);
180             String nextHopIpAddr = null;
181             String nextHopMacAddress = null;
182             String ip = srcPrefix.getIpv4Address().getValue();
183             if (interfaceManager.isExternalInterface(vpnInterface)) {
184                 String subnetId = getSubnetId(vpnName, dstPrefix.getIpv4Address().getValue());
185                 if (subnetId == null) {
186                     LOG.trace("Can't find corresponding subnet for src IP {}, src MAC {}, dst IP {},  in VPN {}",
187                             srcPrefix, mipMacAddress, dstPrefix, vpnName);
188                     return;
189                 }
190                 ip = VpnUtil.getIpPrefix(ip);
191                 AdjacencyBuilder newAdjBuilder =
192                         new AdjacencyBuilder().setIpAddress(ip).setKey(new AdjacencyKey(ip))
193                         .setPrimaryAdjacency(true).setMacAddress(mipMacAddress).setSubnetId(new Uuid(subnetId))
194                         .setPhysNetworkFunc(true);
195
196                 List<Adjacency> adjacencyList = adjacencies.isPresent()
197                         ? adjacencies.get().getAdjacency() : new ArrayList<>();
198
199                 adjacencyList.add(newAdjBuilder.build());
200
201                 Adjacencies aug = VpnUtil.getVpnInterfaceAugmentation(adjacencyList);
202                 VpnInterface newVpnIntf =
203                         new VpnInterfaceBuilder().setKey(new VpnInterfaceKey(vpnInterface))
204                         .setName(vpnInterface).setVpnInstanceName(vpnName).addAugmentation(Adjacencies.class, aug)
205                         .build();
206                 VpnUtil.syncUpdate(dataBroker, LogicalDatastoreType.CONFIGURATION, vpnIfId, newVpnIntf);
207                 LOG.debug(" Successfully stored subnetroute Adjacency into VpnInterface {}", vpnInterface);
208                 return;
209             }
210
211             if (adjacencies.isPresent()) {
212                 List<Adjacency> adjacencyList = adjacencies.get().getAdjacency();
213                 ip = VpnUtil.getIpPrefix(ip);
214                 for (Adjacency adjacs : adjacencyList) {
215                     if (adjacs.isPrimaryAdjacency()) {
216                         nextHopIpAddr = adjacs.getIpAddress();
217                         nextHopMacAddress = adjacs.getMacAddress();
218                         break;
219                     }
220                 }
221                 if (nextHopIpAddr != null) {
222                     String rd = VpnUtil.getVpnRd(dataBroker, vpnName);
223                     long label =
224                         VpnUtil.getUniqueId(idManager, VpnConstants.VPN_IDPOOL_NAME,
225                             VpnUtil.getNextHopLabelKey((rd != null) ? rd : vpnName, ip));
226                     if (label == 0) {
227                         LOG.error("Unable to fetch label from Id Manager. Bailing out of adding MIP adjacency {} "
228                             + "to vpn interface {} for vpn {}", ip, vpnInterface, vpnName);
229                         return;
230                     }
231                     String nextHopIp = nextHopIpAddr.split("/")[0];
232                     AdjacencyBuilder newAdjBuilder =
233                             new AdjacencyBuilder().setIpAddress(ip).setKey(new AdjacencyKey(ip)).setNextHopIpList(
234                                     Collections.singletonList(nextHopIp));
235                     if (mipMacAddress != null && !mipMacAddress.equalsIgnoreCase(nextHopMacAddress)) {
236                         newAdjBuilder.setMacAddress(mipMacAddress);
237                     }
238                     adjacencyList.add(newAdjBuilder.build());
239                     Adjacencies aug = VpnUtil.getVpnInterfaceAugmentation(adjacencyList);
240                     VpnInterface newVpnIntf =
241                         new VpnInterfaceBuilder().setKey(new VpnInterfaceKey(vpnInterface)).setName(
242                             vpnInterface).setVpnInstanceName(vpnName).addAugmentation(Adjacencies.class, aug).build();
243                     VpnUtil.syncUpdate(dataBroker, LogicalDatastoreType.CONFIGURATION, vpnIfId, newVpnIntf);
244                     LOG.debug(" Successfully stored subnetroute Adjacency into VpnInterface {}", vpnInterface);
245                 }
246             }
247         }
248
249     }
250
251     private String getSubnetId(String vpnName, String ip) {
252         // Check if this IP belongs to a router_interface
253         VpnPortipToPort vpnPortipToPort =
254                 VpnUtil.getNeutronPortFromVpnPortFixedIp(dataBroker, vpnName, ip);
255         if (vpnPortipToPort != null && vpnPortipToPort.isSubnetIp()) {
256             List<Adjacency> adjacecnyList = VpnUtil.getAdjacenciesForVpnInterfaceFromConfig(dataBroker,
257                     vpnPortipToPort.getPortName());
258             for (Adjacency adjacency : adjacecnyList) {
259                 if (adjacency.isPrimaryAdjacency()) {
260                     return adjacency.getSubnetId().getValue();
261                 }
262             }
263         }
264
265         // Check if this IP belongs to a router_gateway
266         List<Uuid> routerIds = VpnUtil.getExternalNetworkRouterIds(dataBroker, new Uuid(vpnName));
267         for (Uuid routerId : routerIds) {
268             Uuid subnetId = VpnUtil.getSubnetFromExternalRouterByIp(dataBroker, routerId, ip);
269             if (subnetId != null) {
270                 return subnetId.getValue();
271             }
272         }
273
274         return null;
275     }
276
277     private void removeMipAdjacency(String vpnName, String vpnInterface, IpAddress prefix) {
278         String ip = VpnUtil.getIpPrefix(prefix.getIpv4Address().getValue());
279         LOG.trace("Removing {} adjacency from Old VPN Interface {} ", ip, vpnInterface);
280         InstanceIdentifier<VpnInterface> vpnIfId = VpnUtil.getVpnInterfaceIdentifier(vpnInterface);
281         InstanceIdentifier<Adjacencies> path = vpnIfId.augmentation(Adjacencies.class);
282         synchronized (vpnInterface.intern()) {
283             Optional<Adjacencies> adjacencies = VpnUtil.read(dataBroker, LogicalDatastoreType.OPERATIONAL, path);
284             if (adjacencies.isPresent()) {
285                 InstanceIdentifier<Adjacency> adjacencyIdentifier =
286                     InstanceIdentifier.builder(VpnInterfaces.class).child(VpnInterface.class,
287                         new VpnInterfaceKey(vpnInterface)).augmentation(Adjacencies.class).child(Adjacency.class,
288                         new AdjacencyKey(ip)).build();
289                 MDSALUtil.syncDelete(dataBroker, LogicalDatastoreType.CONFIGURATION, adjacencyIdentifier);
290                 LOG.trace("Successfully Deleted Adjacency into VpnInterface {}", vpnInterface);
291             }
292         }
293     }
294
295     private void putVpnIpToMigrateArpCache(String vpnName, String ipToQuery, PhysAddress srcMac) {
296         long cacheSize = config.getArpCacheSize().longValue();
297         if (migrateArpCache.size() >= cacheSize) {
298             LOG.debug("ARP_MIGRATE_CACHE: max size {} reached, assuming cache eviction we still put IP {}"
299                     + " vpnName {} with MAC {}", cacheSize, ipToQuery, vpnName, srcMac);
300         }
301         LOG.debug("ARP_MIGRATE_CACHE: add to dirty cache IP {} vpnName {} with MAC {}", ipToQuery, vpnName, srcMac);
302         migrateArpCache.put(new ImmutablePair<>(vpnName, ipToQuery),
303                 new BigInteger(String.valueOf(System.currentTimeMillis())));
304     }
305
306     private boolean isIpInArpMigrateCache(String vpnName, String ipToQuery) {
307         if (migrateArpCache == null || migrateArpCache.size() == 0) {
308             return false;
309         }
310         Pair<String, String> keyPair = new ImmutablePair<>(vpnName, ipToQuery);
311         BigInteger prevTimeStampCached = migrateArpCache.getIfPresent(keyPair);
312         if (prevTimeStampCached == null) {
313             LOG.debug("ARP_MIGRATE_CACHE: there is no IP {} vpnName {} in dirty cache, so learn it",
314                     ipToQuery, vpnName);
315             return false;
316         }
317         if (System.currentTimeMillis() > prevTimeStampCached.longValue() + config.getArpLearnTimeout()) {
318             LOG.debug("ARP_MIGRATE_CACHE: older than timeout value - remove from dirty cache IP {} vpnName {}",
319                     ipToQuery, vpnName);
320             migrateArpCache.invalidate(keyPair);
321             return false;
322         }
323         LOG.debug("ARP_MIGRATE_CACHE: younger than timeout value - ignore learning IP {} vpnName {}",
324                 ipToQuery, vpnName);
325         return true;
326     }
327 }