blob: d1dfdd2c594a41c2442122fa03b7f577dd14977f [file] [log] [blame]
Hyunsun Moon022272f2016-01-11 15:30:42 -08001/*
Brian O'Connor8e57fd52016-04-09 01:19:45 -07002 * Copyright 2016-present Open Networking Laboratory
Hyunsun Moon022272f2016-01-11 15:30:42 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
alshabibb4d31712016-06-01 18:51:03 -070016package org.opencord.cordvtn.impl;
Hyunsun Moon022272f2016-01-11 15:30:42 -080017
Hyunsun Moonb5f92e52016-02-17 15:02:06 -080018import com.google.common.collect.Maps;
Hyunsun Moon5401aaa2016-06-12 17:40:34 -070019import com.google.common.collect.Sets;
20import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Deactivate;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
Hyunsun Moon022272f2016-01-11 15:30:42 -080025import org.onlab.packet.ARP;
26import org.onlab.packet.EthType;
27import org.onlab.packet.Ethernet;
28import org.onlab.packet.Ip4Address;
29import org.onlab.packet.IpAddress;
30import org.onlab.packet.MacAddress;
Hyunsun Moonc031d9b2016-08-04 13:57:22 -070031import org.onosproject.net.DeviceId;
32import org.onosproject.net.PortNumber;
Hyunsun Moon5401aaa2016-06-12 17:40:34 -070033import org.onosproject.net.packet.PacketProcessor;
34import org.onosproject.xosclient.api.VtnService;
35import org.opencord.cordvtn.api.CordVtnConfig;
alshabibb4d31712016-06-01 18:51:03 -070036import org.opencord.cordvtn.api.Instance;
Hyunsun Moon3fc17f72016-01-24 21:47:06 -080037import org.onosproject.net.Host;
Hyunsun Moon022272f2016-01-11 15:30:42 -080038import org.onosproject.net.flow.DefaultTrafficSelector;
39import org.onosproject.net.flow.DefaultTrafficTreatment;
40import org.onosproject.net.flow.TrafficSelector;
41import org.onosproject.net.flow.TrafficTreatment;
42import org.onosproject.net.packet.DefaultOutboundPacket;
43import org.onosproject.net.packet.PacketContext;
44import org.onosproject.net.packet.PacketPriority;
45import org.onosproject.net.packet.PacketService;
46import org.slf4j.Logger;
47
48import java.nio.ByteBuffer;
Hyunsun Moonb5f92e52016-02-17 15:02:06 -080049import java.util.Map;
Hyunsun Moon022272f2016-01-11 15:30:42 -080050import java.util.Optional;
51import java.util.Set;
52
53import static com.google.common.base.Preconditions.checkNotNull;
Hyunsun Moon5401aaa2016-06-12 17:40:34 -070054import static org.onosproject.xosclient.api.VtnServiceApi.NetworkType.PRIVATE;
Hyunsun Moon1e88fef2016-08-04 14:00:35 -070055import static org.onosproject.xosclient.api.VtnServiceApi.ServiceType.ACCESS_AGENT;
Hyunsun Moonc031d9b2016-08-04 13:57:22 -070056import static org.onosproject.xosclient.api.VtnServiceApi.ServiceType.MANAGEMENT;
Hyunsun Moon022272f2016-01-11 15:30:42 -080057import static org.slf4j.LoggerFactory.getLogger;
58
59/**
60 * Handles ARP requests for virtual network service IPs.
61 */
Hyunsun Moon5401aaa2016-06-12 17:40:34 -070062@Component(immediate = true)
63public class CordVtnArpProxy extends AbstractInstanceHandler {
Hyunsun Moon022272f2016-01-11 15:30:42 -080064 protected final Logger log = getLogger(getClass());
Hyunsun Moon022272f2016-01-11 15:30:42 -080065
Hyunsun Moon5401aaa2016-06-12 17:40:34 -070066 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
67 protected PacketService packetService;
Hyunsun Moon022272f2016-01-11 15:30:42 -080068
Hyunsun Moonc031d9b2016-08-04 13:57:22 -070069 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
70 protected CordVtnNodeManager nodeManager;
71
Hyunsun Moon5401aaa2016-06-12 17:40:34 -070072 private final PacketProcessor packetProcessor = new InternalPacketProcessor();
Hyunsun Moonb5f92e52016-02-17 15:02:06 -080073 private final Map<Ip4Address, MacAddress> gateways = Maps.newConcurrentMap();
Hyunsun Moon022272f2016-01-11 15:30:42 -080074
Hyunsun Moon5401aaa2016-06-12 17:40:34 -070075 private MacAddress privateGatewayMac = MacAddress.NONE;
76
77 @Activate
78 protected void activate() {
79 super.activate();
80 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
81 requestPacket();
82 }
83
84 @Deactivate
85 protected void deactivate() {
86 packetService.removeProcessor(packetProcessor);
87 super.deactivate();
Hyunsun Moon022272f2016-01-11 15:30:42 -080088 }
89
90 /**
91 * Requests ARP packet.
92 */
Hyunsun Moon5401aaa2016-06-12 17:40:34 -070093 private void requestPacket() {
Hyunsun Moon022272f2016-01-11 15:30:42 -080094 TrafficSelector selector = DefaultTrafficSelector.builder()
95 .matchEthType(EthType.EtherType.ARP.ethType().toShort())
96 .build();
97
98 packetService.requestPackets(selector,
99 PacketPriority.CONTROL,
100 appId,
101 Optional.empty());
102 }
103
104 /**
105 * Cancels ARP packet.
106 */
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700107 private void cancelPacket() {
Hyunsun Moon022272f2016-01-11 15:30:42 -0800108 TrafficSelector selector = DefaultTrafficSelector.builder()
109 .matchEthType(EthType.EtherType.ARP.ethType().toShort())
110 .build();
111
112 packetService.cancelPackets(selector,
113 PacketPriority.CONTROL,
114 appId,
115 Optional.empty());
116 }
117
118 /**
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800119 * Adds a given gateway IP and MAC address to this ARP proxy.
Hyunsun Moon022272f2016-01-11 15:30:42 -0800120 *
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800121 * @param gatewayIp gateway ip address
122 * @param gatewayMac gateway mac address
Hyunsun Moon022272f2016-01-11 15:30:42 -0800123 */
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700124 private void addGateway(IpAddress gatewayIp, MacAddress gatewayMac) {
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800125 checkNotNull(gatewayIp);
126 checkNotNull(gatewayMac);
127 gateways.put(gatewayIp.getIp4Address(), gatewayMac);
Hyunsun Moon022272f2016-01-11 15:30:42 -0800128 }
129
130 /**
131 * Removes a given service IP address from this ARP proxy.
132 *
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800133 * @param gatewayIp gateway ip address
Hyunsun Moon022272f2016-01-11 15:30:42 -0800134 */
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700135 private void removeGateway(IpAddress gatewayIp) {
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800136 checkNotNull(gatewayIp);
137 gateways.remove(gatewayIp.getIp4Address());
Hyunsun Moon022272f2016-01-11 15:30:42 -0800138 }
139
140 /**
141 * Emits ARP reply with fake MAC address for a given ARP request.
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700142 * It only handles requests for the registered gateway IPs and host IPs.
Hyunsun Moon022272f2016-01-11 15:30:42 -0800143 *
144 * @param context packet context
145 * @param ethPacket ethernet packet
146 */
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700147 private void processArpRequest(PacketContext context, Ethernet ethPacket) {
Hyunsun Moon022272f2016-01-11 15:30:42 -0800148 ARP arpPacket = (ARP) ethPacket.getPayload();
Hyunsun Moonb6febbe2016-02-12 15:59:53 -0800149 Ip4Address targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
Hyunsun Moonb6febbe2016-02-12 15:59:53 -0800150
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800151 MacAddress gatewayMac = gateways.get(targetIp);
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700152 MacAddress replyMac = gatewayMac != null ? gatewayMac :
153 getMacFromHostService(targetIp);
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800154
155 if (replyMac.equals(MacAddress.NONE)) {
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700156 log.trace("Failed to find MAC for {}", targetIp);
157 forwardManagementArpRequest(context, ethPacket);
Hyunsun Moon0d836e22016-02-01 23:30:58 -0800158 return;
159 }
160
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700161 log.trace("Send ARP reply for {} with {}", targetIp, replyMac);
Hyunsun Moon022272f2016-01-11 15:30:42 -0800162 Ethernet ethReply = ARP.buildArpReply(
163 targetIp,
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800164 replyMac,
Hyunsun Moon022272f2016-01-11 15:30:42 -0800165 ethPacket);
166
167 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
168 .setOutput(context.inPacket().receivedFrom().port())
169 .build();
170
171 packetService.emit(new DefaultOutboundPacket(
172 context.inPacket().receivedFrom().deviceId(),
173 treatment,
174 ByteBuffer.wrap(ethReply.serialize())));
175
176 context.block();
177 }
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800178
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700179 private void processArpReply(PacketContext context, Ethernet ethPacket) {
180 ARP arpPacket = (ARP) ethPacket.getPayload();
181 Ip4Address targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
182
183 DeviceId deviceId = context.inPacket().receivedFrom().deviceId();
184 Host host = hostService.getHostsByIp(targetIp).stream()
185 .filter(h -> h.location().deviceId().equals(deviceId))
186 .findFirst()
187 .orElse(null);
188
189 if (host == null) {
190 // do nothing for the unknown ARP reply
191 log.trace("No host found for {} in {}", targetIp, deviceId);
192 context.block();
193 return;
194 }
195
196 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
197 .setOutput(host.location().port())
198 .build();
199
200 packetService.emit(new DefaultOutboundPacket(
201 deviceId,
202 treatment,
203 ByteBuffer.wrap(ethPacket.serialize())));
204
205 context.block();
206 }
207
208 private void forwardManagementArpRequest(PacketContext context, Ethernet ethPacket) {
209 DeviceId deviceId = context.inPacket().receivedFrom().deviceId();
210 PortNumber hostMgmtPort = nodeManager.hostManagementPort(deviceId);
211 Host host = hostService.getConnectedHosts(context.inPacket().receivedFrom())
212 .stream()
213 .findFirst().orElse(null);
214
215 if (host == null ||
216 !Instance.of(host).serviceType().equals(MANAGEMENT) ||
217 hostMgmtPort == null) {
218 context.block();
219 return;
220 }
221
222 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
223 .setOutput(hostMgmtPort)
224 .build();
225
226 packetService.emit(new DefaultOutboundPacket(
227 context.inPacket().receivedFrom().deviceId(),
228 treatment,
229 ByteBuffer.wrap(ethPacket.serialize())));
230
231 log.trace("Forward ARP request to management network");
232 context.block();
233 }
234
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800235 /**
236 * Emits gratuitous ARP when a gateway mac address has been changed.
237 *
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800238 * @param gatewayIp gateway ip address to update MAC
Hyunsun Moone7e4bb32016-05-16 04:32:45 -0700239 * @param instances set of instances to send gratuitous ARP packet
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800240 */
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700241 private void sendGratuitousArp(IpAddress gatewayIp, Set<Instance> instances) {
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800242 MacAddress gatewayMac = gateways.get(gatewayIp.getIp4Address());
243 if (gatewayMac == null) {
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700244 log.debug("Gateway {} is not registered to ARP proxy", gatewayIp);
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800245 return;
246 }
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800247
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800248 Ethernet ethArp = buildGratuitousArp(gatewayIp.getIp4Address(), gatewayMac);
Hyunsun Moone7e4bb32016-05-16 04:32:45 -0700249 instances.stream().forEach(instance -> {
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800250 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Hyunsun Moone7e4bb32016-05-16 04:32:45 -0700251 .setOutput(instance.portNumber())
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800252 .build();
253
254 packetService.emit(new DefaultOutboundPacket(
Hyunsun Moone7e4bb32016-05-16 04:32:45 -0700255 instance.deviceId(),
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800256 treatment,
257 ByteBuffer.wrap(ethArp.serialize())));
258 });
259 }
260
261 /**
262 * Builds gratuitous ARP packet with a given IP and MAC address.
263 *
264 * @param ip ip address for TPA and SPA
265 * @param mac new mac address
266 * @return ethernet packet
267 */
268 private Ethernet buildGratuitousArp(IpAddress ip, MacAddress mac) {
269 Ethernet eth = new Ethernet();
270
271 eth.setEtherType(Ethernet.TYPE_ARP);
272 eth.setSourceMACAddress(mac);
273 eth.setDestinationMACAddress(MacAddress.BROADCAST);
274
275 ARP arp = new ARP();
276 arp.setOpCode(ARP.OP_REQUEST);
277 arp.setHardwareType(ARP.HW_TYPE_ETHERNET);
278 arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
279 arp.setProtocolType(ARP.PROTO_TYPE_IP);
280 arp.setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH);
281
282 arp.setSenderHardwareAddress(mac.toBytes());
283 arp.setTargetHardwareAddress(MacAddress.BROADCAST.toBytes());
284 arp.setSenderProtocolAddress(ip.getIp4Address().toOctets());
285 arp.setTargetProtocolAddress(ip.getIp4Address().toOctets());
286
287 eth.setPayload(arp);
288 return eth;
289 }
Hyunsun Moonb6febbe2016-02-12 15:59:53 -0800290
291 /**
292 * Returns MAC address of a host with a given target IP address by asking to
293 * host service. It does not support overlapping IP.
294 *
295 * @param targetIp target ip
296 * @return mac address, or NONE mac address if it fails to find the mac
297 */
298 private MacAddress getMacFromHostService(IpAddress targetIp) {
299 checkNotNull(targetIp);
300
301 Host host = hostService.getHostsByIp(targetIp)
302 .stream()
303 .findFirst()
304 .orElse(null);
305
306 if (host != null) {
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700307 log.trace("Found MAC from host service for {}", targetIp);
Hyunsun Moonb6febbe2016-02-12 15:59:53 -0800308 return host.mac();
309 } else {
310 return MacAddress.NONE;
311 }
312 }
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700313
314 private class InternalPacketProcessor implements PacketProcessor {
315
316 @Override
317 public void process(PacketContext context) {
318 if (context.isHandled()) {
319 return;
320 }
321 Ethernet ethPacket = context.inPacket().parsed();
322 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
323 return;
324 }
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700325
326 ARP arpPacket = (ARP) ethPacket.getPayload();
327 switch (arpPacket.getOpCode()) {
328 case ARP.OP_REQUEST:
329 processArpRequest(context, ethPacket);
330 break;
331 case ARP.OP_REPLY:
332 processArpReply(context, ethPacket);
333 break;
334 default:
335 break;
336 }
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700337 }
338 }
339
340 @Override
341 public void instanceDetected(Instance instance) {
Hyunsun Moon1e88fef2016-08-04 14:00:35 -0700342 // TODO remove this when XOS provides access agent information
343 // and handle it the same way wit the other instances
344 if (instance.serviceType() == ACCESS_AGENT) {
345 return;
346 }
347
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700348 VtnService service = getVtnService(instance.serviceId());
349 if (service == null) {
350 return;
351 }
352
353 if (service.networkType().equals(PRIVATE)) {
354 log.trace("Added IP:{} MAC:{}", service.serviceIp(), privateGatewayMac);
355 addGateway(service.serviceIp(), privateGatewayMac);
356 // send gratuitous ARP for the existing VMs when ONOS is restarted
357 sendGratuitousArp(service.serviceIp(), Sets.newHashSet(instance));
358 }
359 }
360
361 @Override
362 public void instanceRemoved(Instance instance) {
Hyunsun Moon1e88fef2016-08-04 14:00:35 -0700363 // TODO remove this when XOS provides access agent information
364 // and handle it the same way wit the other instances
365 if (instance.serviceType() == ACCESS_AGENT) {
366 return;
367 }
368
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700369 VtnService service = getVtnService(instance.serviceId());
370 if (service == null) {
371 return;
372 }
373
374 // remove this network gateway from proxy ARP if no instance presents
375 if (service.networkType().equals(PRIVATE) &&
376 getInstances(service.id()).isEmpty()) {
377 removeGateway(service.serviceIp());
378 }
379 }
380
381 @Override
382 protected void readConfiguration() {
383 CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
384 if (config == null) {
385 log.debug("No configuration found");
386 return;
387 }
388
389 privateGatewayMac = config.privateGatewayMac();
390 log.debug("Added private gateway MAC {}", privateGatewayMac);
391
392 config.publicGateways().entrySet().stream().forEach(entry -> {
393 addGateway(entry.getKey(), entry.getValue());
394 log.debug("Added public gateway IP {}, MAC {}",
395 entry.getKey(), entry.getValue());
396 });
397 // TODO send gratuitous arp in case the MAC is changed
398
399 super.readConfiguration();
400 }
Hyunsun Moon022272f2016-01-11 15:30:42 -0800401}