2 * Copyright (c) 2016 Ericsson India Global Services Pvt Ltd. and others. All rights reserved.
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
8 package org.opendaylight.netvirt.vpnmanager;
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.net.UnknownHostException;
15 import java.util.ArrayList;
16 import java.util.Collections;
17 import java.util.List;
18 import java.util.Objects;
19 import java.util.concurrent.TimeUnit;
20 import javax.inject.Inject;
21 import javax.inject.Singleton;
22 import org.apache.commons.lang3.tuple.ImmutablePair;
23 import org.apache.commons.lang3.tuple.Pair;
24 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
25 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
26 import org.opendaylight.genius.interfacemanager.interfaces.IInterfaceManager;
27 import org.opendaylight.genius.mdsalutil.MDSALUtil;
28 import org.opendaylight.genius.mdsalutil.NWUtil;
29 import org.opendaylight.yang.gen.v1.urn.huawei.params.xml.ns.yang.l3vpn.rev140815.VpnInterfaces;
30 import org.opendaylight.yang.gen.v1.urn.huawei.params.xml.ns.yang.l3vpn.rev140815.vpn.interfaces.VpnInterface;
31 import org.opendaylight.yang.gen.v1.urn.huawei.params.xml.ns.yang.l3vpn.rev140815.vpn.interfaces.VpnInterfaceBuilder;
32 import org.opendaylight.yang.gen.v1.urn.huawei.params.xml.ns.yang.l3vpn.rev140815.vpn.interfaces.VpnInterfaceKey;
33 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpAddress;
34 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.PhysAddress;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Uuid;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.arputil.rev160406.ArpRequestReceived;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.arputil.rev160406.ArpResponseReceived;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.arputil.rev160406.MacChanged;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.arputil.rev160406.OdlArputilListener;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.idmanager.rev160406.IdManagerService;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.Adjacencies;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.adjacency.list.Adjacency;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.adjacency.list.Adjacency.AdjacencyType;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.adjacency.list.AdjacencyBuilder;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.adjacency.list.AdjacencyKey;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.l3vpn.rev130911.learnt.vpn.vip.to.port.data.LearntVpnVipToPort;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.neutronvpn.rev150602.neutron.vpn.portip.port.data.VpnPortipToPort;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.vpn.config.rev161130.VpnConfig;
49 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
50 import org.slf4j.Logger;
51 import org.slf4j.LoggerFactory;
54 public class ArpNotificationHandler implements OdlArputilListener {
55 private static final Logger LOG = LoggerFactory.getLogger(ArpNotificationHandler.class);
56 // temp where Key is VPNInstance+IP and value is timestamp
57 private final Cache<Pair<String, String>, BigInteger> migrateArpCache;
59 private final DataBroker dataBroker;
60 private final IdManagerService idManager;
61 private final IInterfaceManager interfaceManager;
62 private final VpnConfig config;
65 public ArpNotificationHandler(DataBroker dataBroker, IdManagerService idManager,
66 IInterfaceManager interfaceManager, VpnConfig vpnConfig) {
67 this.dataBroker = dataBroker;
68 this.idManager = idManager;
69 this.interfaceManager = interfaceManager;
70 this.config = vpnConfig;
72 long duration = config.getArpLearnTimeout() * 10;
73 long cacheSize = config.getArpCacheSize().longValue();
75 CacheBuilder.newBuilder().maximumSize(cacheSize).expireAfterWrite(duration,
76 TimeUnit.MILLISECONDS).build();
80 public void onMacChanged(MacChanged notification) {
85 public void onArpRequestReceived(ArpRequestReceived notification) {
86 String srcInterface = notification.getInterface();
87 IpAddress srcIP = notification.getSrcIpaddress();
88 PhysAddress srcMac = notification.getSrcMac();
89 IpAddress targetIP = notification.getDstIpaddress();
90 BigInteger metadata = notification.getMetadata();
91 boolean isGarp = srcIP.equals(targetIP);
93 LOG.info("ArpNotification Non-Gratuitous Request Received from "
94 + "interface {} and IP {} having MAC {} target destination {}, ignoring..",
95 srcInterface, srcIP.getIpv4Address().getValue(),srcMac.getValue(),
96 targetIP.getIpv4Address().getValue());
99 LOG.info("ArpNotification Gratuitous Request Received from "
100 + "interface {} and IP {} having MAC {} target destination {}, learning MAC",
101 srcInterface, srcIP.getIpv4Address().getValue(),srcMac.getValue(),
102 targetIP.getIpv4Address().getValue());
103 processArpLearning(srcInterface, srcIP, srcMac, metadata, targetIP);
107 public void onArpResponseReceived(ArpResponseReceived notification) {
108 String srcInterface = notification.getInterface();
109 IpAddress srcIP = notification.getSrcIpaddress();
110 PhysAddress srcMac = notification.getSrcMac();
111 LOG.info("ArpNotification Response Received from interface {} and IP {} having MAC {}, learning MAC",
112 srcInterface, srcIP.getIpv4Address().getValue(), srcMac.getValue());
113 List<Adjacency> adjacencies = VpnUtil.getAdjacenciesForVpnInterfaceFromConfig(dataBroker, srcInterface);
114 if (adjacencies != null) {
115 for (Adjacency adj : adjacencies) {
116 String ipAddress = adj.getIpAddress();
118 if (NWUtil.isIpInSubnet(NWUtil.ipAddressToInt(srcIP.getIpv4Address().getValue()), ipAddress)) {
121 } catch (UnknownHostException e) {
122 LOG.error("Subnet string {} not convertible to InetAdddress", srcIP, e);
126 BigInteger metadata = notification.getMetadata();
127 IpAddress targetIP = notification.getDstIpaddress();
128 LOG.trace("ArpNotification Response Received from interface {} and IP {} having MAC {}, learning MAC",
129 srcInterface, srcIP.getIpv4Address().getValue(), srcMac.getValue());
130 processArpLearning(srcInterface, srcIP, srcMac, metadata, targetIP);
133 private void processArpLearning(String srcInterface, IpAddress srcIP, PhysAddress srcMac, BigInteger metadata,
135 if (metadata != null && !Objects.equals(metadata, BigInteger.ZERO)) {
136 Optional<String> vpn = VpnUtil.getVpnAssociatedWithInterface(dataBroker, srcInterface);
137 if (vpn.isPresent()) {
138 String vpnName = vpn.get();
139 LOG.info("Received ARP for sender MAC {} and sender IP {} via interface {}",
140 srcMac.getValue(), srcIP.getIpv4Address().getValue(), srcInterface);
141 String ipToQuery = srcIP.getIpv4Address().getValue();
142 LOG.info("ARP being processed for Source IP {}", ipToQuery);
143 VpnPortipToPort vpnPortipToPort =
144 VpnUtil.getNeutronPortFromVpnPortFixedIp(dataBroker, vpnName, ipToQuery);
145 if (vpnPortipToPort != null) {
146 /* This is a well known neutron port and so should be ignored
147 * from being discovered
151 LearntVpnVipToPort learntVpnVipToPort = VpnUtil.getLearntVpnVipToPort(dataBroker, vpnName, ipToQuery);
152 if (learntVpnVipToPort != null) {
153 String oldPortName = learntVpnVipToPort.getPortName();
154 String oldMac = learntVpnVipToPort.getMacAddress();
155 if (!oldMac.equalsIgnoreCase(srcMac.getValue())) {
156 //MAC has changed for requested IP
157 LOG.info("ARP Source IP/MAC data modified for IP {} with MAC {} and Port {}",
158 ipToQuery, srcMac, srcInterface);
159 synchronized ((vpnName + ipToQuery).intern()) {
160 removeMipAdjacency(vpnName, oldPortName, srcIP);
161 VpnUtil.removeLearntVpnVipToPort(dataBroker, vpnName, ipToQuery);
162 putVpnIpToMigrateArpCache(vpnName, ipToQuery, srcMac);
165 } else if (!isIpInArpMigrateCache(vpnName, ipToQuery)) {
166 learnMacFromArpPackets(vpnName, srcInterface, srcIP, srcMac, dstIP);
169 LOG.info("ARP NO_RESOLVE: VPN not configured. Ignoring responding to ARP requests from this"
170 + " Interface {}.", srcInterface);
177 private void learnMacFromArpPackets(String vpnName, String srcInterface,
178 IpAddress srcIP, PhysAddress srcMac, IpAddress dstIP) {
179 String ipToQuery = srcIP.getIpv4Address().getValue();
180 synchronized ((vpnName + ipToQuery).intern()) {
181 VpnUtil.createLearntVpnVipToPort(dataBroker, vpnName, ipToQuery, srcInterface, srcMac.getValue());
182 addMipAdjacency(vpnName, srcInterface, srcIP, srcMac.getValue(), dstIP);
186 private void addMipAdjacency(String vpnName, String vpnInterface, IpAddress srcPrefix, String mipMacAddress,
187 IpAddress dstPrefix) {
188 LOG.trace("Adding {} adjacency to VPN Interface {} ",srcPrefix,vpnInterface);
189 InstanceIdentifier<VpnInterface> vpnIfId = VpnUtil.getVpnInterfaceIdentifier(vpnInterface);
190 InstanceIdentifier<Adjacencies> path = vpnIfId.augmentation(Adjacencies.class);
191 synchronized (vpnInterface.intern()) {
192 Optional<Adjacencies> adjacencies = VpnUtil.read(dataBroker, LogicalDatastoreType.CONFIGURATION, path);
193 String nextHopIpAddr = null;
194 String nextHopMacAddress = null;
195 String ip = srcPrefix.getIpv4Address().getValue();
196 if (interfaceManager.isExternalInterface(vpnInterface)) {
197 String subnetId = getSubnetId(vpnName, dstPrefix.getIpv4Address().getValue());
198 if (subnetId == null) {
199 LOG.trace("Can't find corresponding subnet for src IP {}, src MAC {}, dst IP {}, in VPN {}",
200 srcPrefix, mipMacAddress, dstPrefix, vpnName);
203 ip = VpnUtil.getIpPrefix(ip);
204 AdjacencyBuilder newAdjBuilder = new AdjacencyBuilder().setIpAddress(ip).setKey(new AdjacencyKey(ip))
205 .setAdjacencyType(AdjacencyType.PrimaryAdjacency).setMacAddress(mipMacAddress)
206 .setSubnetId(new Uuid(subnetId)).setPhysNetworkFunc(true);
208 List<Adjacency> adjacencyList = adjacencies.isPresent()
209 ? adjacencies.get().getAdjacency() : new ArrayList<>();
211 adjacencyList.add(newAdjBuilder.build());
213 Adjacencies aug = VpnUtil.getVpnInterfaceAugmentation(adjacencyList);
214 VpnInterface newVpnIntf =
215 new VpnInterfaceBuilder().setKey(new VpnInterfaceKey(vpnInterface))
216 .setName(vpnInterface).setVpnInstanceName(vpnName).addAugmentation(Adjacencies.class, aug)
218 VpnUtil.syncUpdate(dataBroker, LogicalDatastoreType.CONFIGURATION, vpnIfId, newVpnIntf);
219 LOG.debug(" Successfully stored subnetroute Adjacency into VpnInterface {}", vpnInterface);
223 if (adjacencies.isPresent()) {
224 List<Adjacency> adjacencyList = adjacencies.get().getAdjacency();
225 ip = VpnUtil.getIpPrefix(ip);
226 for (Adjacency adjacs : adjacencyList) {
227 if (adjacs.getAdjacencyType() == AdjacencyType.PrimaryAdjacency) {
228 nextHopIpAddr = adjacs.getIpAddress();
229 nextHopMacAddress = adjacs.getMacAddress();
233 if (nextHopIpAddr != null) {
234 String rd = VpnUtil.getVpnRd(dataBroker, vpnName);
236 VpnUtil.getUniqueId(idManager, VpnConstants.VPN_IDPOOL_NAME,
237 VpnUtil.getNextHopLabelKey(rd != null ? rd : vpnName, ip));
239 LOG.error("Unable to fetch label from Id Manager. Bailing out of adding MIP adjacency {} "
240 + "to vpn interface {} for vpn {}", ip, vpnInterface, vpnName);
243 String nextHopIp = nextHopIpAddr.split("/")[0];
244 AdjacencyBuilder newAdjBuilder =
245 new AdjacencyBuilder().setIpAddress(ip).setKey(new AdjacencyKey(ip)).setNextHopIpList(
246 Collections.singletonList(nextHopIp)).setAdjacencyType(AdjacencyType.LearntIp);
247 if (mipMacAddress != null && !mipMacAddress.equalsIgnoreCase(nextHopMacAddress)) {
248 newAdjBuilder.setMacAddress(mipMacAddress);
250 adjacencyList.add(newAdjBuilder.build());
251 Adjacencies aug = VpnUtil.getVpnInterfaceAugmentation(adjacencyList);
252 VpnInterface newVpnIntf =
253 new VpnInterfaceBuilder().setKey(new VpnInterfaceKey(vpnInterface)).setName(
254 vpnInterface).setVpnInstanceName(vpnName).addAugmentation(Adjacencies.class, aug).build();
255 VpnUtil.syncUpdate(dataBroker, LogicalDatastoreType.CONFIGURATION, vpnIfId, newVpnIntf);
256 LOG.debug(" Successfully stored subnetroute Adjacency into VpnInterface {}", vpnInterface);
263 private String getSubnetId(String vpnName, String ip) {
264 // Check if this IP belongs to a router_interface
265 VpnPortipToPort vpnPortipToPort =
266 VpnUtil.getNeutronPortFromVpnPortFixedIp(dataBroker, vpnName, ip);
267 if (vpnPortipToPort != null && vpnPortipToPort.isSubnetIp()) {
268 List<Adjacency> adjacecnyList = VpnUtil.getAdjacenciesForVpnInterfaceFromConfig(dataBroker,
269 vpnPortipToPort.getPortName());
270 for (Adjacency adjacency : adjacecnyList) {
271 if (adjacency.getAdjacencyType() == AdjacencyType.PrimaryAdjacency) {
272 return adjacency.getSubnetId().getValue();
277 // Check if this IP belongs to a router_gateway
278 List<Uuid> routerIds = VpnUtil.getExternalNetworkRouterIds(dataBroker, new Uuid(vpnName));
279 for (Uuid routerId : routerIds) {
280 Uuid subnetId = VpnUtil.getSubnetFromExternalRouterByIp(dataBroker, routerId, ip);
281 if (subnetId != null) {
282 return subnetId.getValue();
289 private void removeMipAdjacency(String vpnName, String vpnInterface, IpAddress prefix) {
290 String ip = VpnUtil.getIpPrefix(prefix.getIpv4Address().getValue());
291 LOG.trace("Removing {} adjacency from Old VPN Interface {} ", ip, vpnInterface);
292 synchronized (vpnInterface.intern()) {
293 InstanceIdentifier<Adjacency> adjacencyIdentifier =
294 InstanceIdentifier.builder(VpnInterfaces.class).child(VpnInterface.class,
295 new VpnInterfaceKey(vpnInterface)).augmentation(Adjacencies.class).child(Adjacency.class,
296 new AdjacencyKey(ip)).build();
297 Optional<Adjacency> adjacency = VpnUtil.read(dataBroker, LogicalDatastoreType.OPERATIONAL,
298 adjacencyIdentifier);
299 if (adjacency.isPresent()) {
300 MDSALUtil.syncDelete(dataBroker, LogicalDatastoreType.CONFIGURATION, adjacencyIdentifier);
301 LOG.info("Successfully deleted the learned-ip-adjacency for prefix {} on vpn {} for interface {}",
302 ip, vpnName, vpnInterface);
307 private void putVpnIpToMigrateArpCache(String vpnName, String ipToQuery, PhysAddress srcMac) {
308 long cacheSize = config.getArpCacheSize().longValue();
309 if (migrateArpCache.size() >= cacheSize) {
310 LOG.debug("ARP_MIGRATE_CACHE: max size {} reached, assuming cache eviction we still put IP {}"
311 + " vpnName {} with MAC {}", cacheSize, ipToQuery, vpnName, srcMac);
313 LOG.debug("ARP_MIGRATE_CACHE: add to dirty cache IP {} vpnName {} with MAC {}", ipToQuery, vpnName, srcMac);
314 migrateArpCache.put(new ImmutablePair<>(vpnName, ipToQuery),
315 new BigInteger(String.valueOf(System.currentTimeMillis())));
318 private boolean isIpInArpMigrateCache(String vpnName, String ipToQuery) {
319 if (migrateArpCache == null || migrateArpCache.size() == 0) {
322 Pair<String, String> keyPair = new ImmutablePair<>(vpnName, ipToQuery);
323 BigInteger prevTimeStampCached = migrateArpCache.getIfPresent(keyPair);
324 if (prevTimeStampCached == null) {
325 LOG.debug("ARP_MIGRATE_CACHE: there is no IP {} vpnName {} in dirty cache, so learn it",
329 if (System.currentTimeMillis() > prevTimeStampCached.longValue() + config.getArpLearnTimeout()) {
330 LOG.debug("ARP_MIGRATE_CACHE: older than timeout value - remove from dirty cache IP {} vpnName {}",
332 migrateArpCache.invalidate(keyPair);
335 LOG.debug("ARP_MIGRATE_CACHE: younger than timeout value - ignore learning IP {} vpnName {}",