blob: 1697f1ae4b9e773f86155d35eaaf47171ca56a16 [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 Moonc031d9b2016-08-04 13:57:22 -070055import static org.onosproject.xosclient.api.VtnServiceApi.ServiceType.MANAGEMENT;
Hyunsun Moon022272f2016-01-11 15:30:42 -080056import static org.slf4j.LoggerFactory.getLogger;
57
58/**
59 * Handles ARP requests for virtual network service IPs.
60 */
Hyunsun Moon5401aaa2016-06-12 17:40:34 -070061@Component(immediate = true)
62public class CordVtnArpProxy extends AbstractInstanceHandler {
Hyunsun Moon022272f2016-01-11 15:30:42 -080063 protected final Logger log = getLogger(getClass());
Hyunsun Moon022272f2016-01-11 15:30:42 -080064
Hyunsun Moon5401aaa2016-06-12 17:40:34 -070065 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
66 protected PacketService packetService;
Hyunsun Moon022272f2016-01-11 15:30:42 -080067
Hyunsun Moonc031d9b2016-08-04 13:57:22 -070068 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
69 protected CordVtnNodeManager nodeManager;
70
Hyunsun Moon5401aaa2016-06-12 17:40:34 -070071 private final PacketProcessor packetProcessor = new InternalPacketProcessor();
Hyunsun Moonb5f92e52016-02-17 15:02:06 -080072 private final Map<Ip4Address, MacAddress> gateways = Maps.newConcurrentMap();
Hyunsun Moon022272f2016-01-11 15:30:42 -080073
Hyunsun Moon5401aaa2016-06-12 17:40:34 -070074 private MacAddress privateGatewayMac = MacAddress.NONE;
75
76 @Activate
77 protected void activate() {
78 super.activate();
79 packetService.addProcessor(packetProcessor, PacketProcessor.director(0));
80 requestPacket();
81 }
82
83 @Deactivate
84 protected void deactivate() {
85 packetService.removeProcessor(packetProcessor);
86 super.deactivate();
Hyunsun Moon022272f2016-01-11 15:30:42 -080087 }
88
89 /**
90 * Requests ARP packet.
91 */
Hyunsun Moon5401aaa2016-06-12 17:40:34 -070092 private void requestPacket() {
Hyunsun Moon022272f2016-01-11 15:30:42 -080093 TrafficSelector selector = DefaultTrafficSelector.builder()
94 .matchEthType(EthType.EtherType.ARP.ethType().toShort())
95 .build();
96
97 packetService.requestPackets(selector,
98 PacketPriority.CONTROL,
99 appId,
100 Optional.empty());
101 }
102
103 /**
104 * Cancels ARP packet.
105 */
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700106 private void cancelPacket() {
Hyunsun Moon022272f2016-01-11 15:30:42 -0800107 TrafficSelector selector = DefaultTrafficSelector.builder()
108 .matchEthType(EthType.EtherType.ARP.ethType().toShort())
109 .build();
110
111 packetService.cancelPackets(selector,
112 PacketPriority.CONTROL,
113 appId,
114 Optional.empty());
115 }
116
117 /**
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800118 * Adds a given gateway IP and MAC address to this ARP proxy.
Hyunsun Moon022272f2016-01-11 15:30:42 -0800119 *
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800120 * @param gatewayIp gateway ip address
121 * @param gatewayMac gateway mac address
Hyunsun Moon022272f2016-01-11 15:30:42 -0800122 */
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700123 private void addGateway(IpAddress gatewayIp, MacAddress gatewayMac) {
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800124 checkNotNull(gatewayIp);
125 checkNotNull(gatewayMac);
126 gateways.put(gatewayIp.getIp4Address(), gatewayMac);
Hyunsun Moon022272f2016-01-11 15:30:42 -0800127 }
128
129 /**
130 * Removes a given service IP address from this ARP proxy.
131 *
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800132 * @param gatewayIp gateway ip address
Hyunsun Moon022272f2016-01-11 15:30:42 -0800133 */
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700134 private void removeGateway(IpAddress gatewayIp) {
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800135 checkNotNull(gatewayIp);
136 gateways.remove(gatewayIp.getIp4Address());
Hyunsun Moon022272f2016-01-11 15:30:42 -0800137 }
138
139 /**
140 * Emits ARP reply with fake MAC address for a given ARP request.
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700141 * It only handles requests for the registered gateway IPs and host IPs.
Hyunsun Moon022272f2016-01-11 15:30:42 -0800142 *
143 * @param context packet context
144 * @param ethPacket ethernet packet
145 */
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700146 private void processArpRequest(PacketContext context, Ethernet ethPacket) {
Hyunsun Moon022272f2016-01-11 15:30:42 -0800147 ARP arpPacket = (ARP) ethPacket.getPayload();
Hyunsun Moonb6febbe2016-02-12 15:59:53 -0800148 Ip4Address targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
Hyunsun Moonb6febbe2016-02-12 15:59:53 -0800149
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800150 MacAddress gatewayMac = gateways.get(targetIp);
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700151 MacAddress replyMac = gatewayMac != null ? gatewayMac :
152 getMacFromHostService(targetIp);
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800153
154 if (replyMac.equals(MacAddress.NONE)) {
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700155 log.trace("Failed to find MAC for {}", targetIp);
156 forwardManagementArpRequest(context, ethPacket);
Hyunsun Moon0d836e22016-02-01 23:30:58 -0800157 return;
158 }
159
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700160 log.trace("Send ARP reply for {} with {}", targetIp, replyMac);
Hyunsun Moon022272f2016-01-11 15:30:42 -0800161 Ethernet ethReply = ARP.buildArpReply(
162 targetIp,
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800163 replyMac,
Hyunsun Moon022272f2016-01-11 15:30:42 -0800164 ethPacket);
165
166 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
167 .setOutput(context.inPacket().receivedFrom().port())
168 .build();
169
170 packetService.emit(new DefaultOutboundPacket(
171 context.inPacket().receivedFrom().deviceId(),
172 treatment,
173 ByteBuffer.wrap(ethReply.serialize())));
174
175 context.block();
176 }
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800177
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700178 private void processArpReply(PacketContext context, Ethernet ethPacket) {
179 ARP arpPacket = (ARP) ethPacket.getPayload();
180 Ip4Address targetIp = Ip4Address.valueOf(arpPacket.getTargetProtocolAddress());
181
182 DeviceId deviceId = context.inPacket().receivedFrom().deviceId();
183 Host host = hostService.getHostsByIp(targetIp).stream()
184 .filter(h -> h.location().deviceId().equals(deviceId))
185 .findFirst()
186 .orElse(null);
187
188 if (host == null) {
189 // do nothing for the unknown ARP reply
190 log.trace("No host found for {} in {}", targetIp, deviceId);
191 context.block();
192 return;
193 }
194
195 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
196 .setOutput(host.location().port())
197 .build();
198
199 packetService.emit(new DefaultOutboundPacket(
200 deviceId,
201 treatment,
202 ByteBuffer.wrap(ethPacket.serialize())));
203
204 context.block();
205 }
206
207 private void forwardManagementArpRequest(PacketContext context, Ethernet ethPacket) {
208 DeviceId deviceId = context.inPacket().receivedFrom().deviceId();
209 PortNumber hostMgmtPort = nodeManager.hostManagementPort(deviceId);
210 Host host = hostService.getConnectedHosts(context.inPacket().receivedFrom())
211 .stream()
212 .findFirst().orElse(null);
213
214 if (host == null ||
215 !Instance.of(host).serviceType().equals(MANAGEMENT) ||
216 hostMgmtPort == null) {
217 context.block();
218 return;
219 }
220
221 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
222 .setOutput(hostMgmtPort)
223 .build();
224
225 packetService.emit(new DefaultOutboundPacket(
226 context.inPacket().receivedFrom().deviceId(),
227 treatment,
228 ByteBuffer.wrap(ethPacket.serialize())));
229
230 log.trace("Forward ARP request to management network");
231 context.block();
232 }
233
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800234 /**
235 * Emits gratuitous ARP when a gateway mac address has been changed.
236 *
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800237 * @param gatewayIp gateway ip address to update MAC
Hyunsun Moone7e4bb32016-05-16 04:32:45 -0700238 * @param instances set of instances to send gratuitous ARP packet
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800239 */
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700240 private void sendGratuitousArp(IpAddress gatewayIp, Set<Instance> instances) {
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800241 MacAddress gatewayMac = gateways.get(gatewayIp.getIp4Address());
242 if (gatewayMac == null) {
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700243 log.debug("Gateway {} is not registered to ARP proxy", gatewayIp);
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800244 return;
245 }
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800246
Hyunsun Moonb5f92e52016-02-17 15:02:06 -0800247 Ethernet ethArp = buildGratuitousArp(gatewayIp.getIp4Address(), gatewayMac);
Hyunsun Moone7e4bb32016-05-16 04:32:45 -0700248 instances.stream().forEach(instance -> {
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800249 TrafficTreatment treatment = DefaultTrafficTreatment.builder()
Hyunsun Moone7e4bb32016-05-16 04:32:45 -0700250 .setOutput(instance.portNumber())
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800251 .build();
252
253 packetService.emit(new DefaultOutboundPacket(
Hyunsun Moone7e4bb32016-05-16 04:32:45 -0700254 instance.deviceId(),
Hyunsun Moon3fc17f72016-01-24 21:47:06 -0800255 treatment,
256 ByteBuffer.wrap(ethArp.serialize())));
257 });
258 }
259
260 /**
261 * Builds gratuitous ARP packet with a given IP and MAC address.
262 *
263 * @param ip ip address for TPA and SPA
264 * @param mac new mac address
265 * @return ethernet packet
266 */
267 private Ethernet buildGratuitousArp(IpAddress ip, MacAddress mac) {
268 Ethernet eth = new Ethernet();
269
270 eth.setEtherType(Ethernet.TYPE_ARP);
271 eth.setSourceMACAddress(mac);
272 eth.setDestinationMACAddress(MacAddress.BROADCAST);
273
274 ARP arp = new ARP();
275 arp.setOpCode(ARP.OP_REQUEST);
276 arp.setHardwareType(ARP.HW_TYPE_ETHERNET);
277 arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
278 arp.setProtocolType(ARP.PROTO_TYPE_IP);
279 arp.setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH);
280
281 arp.setSenderHardwareAddress(mac.toBytes());
282 arp.setTargetHardwareAddress(MacAddress.BROADCAST.toBytes());
283 arp.setSenderProtocolAddress(ip.getIp4Address().toOctets());
284 arp.setTargetProtocolAddress(ip.getIp4Address().toOctets());
285
286 eth.setPayload(arp);
287 return eth;
288 }
Hyunsun Moonb6febbe2016-02-12 15:59:53 -0800289
290 /**
291 * Returns MAC address of a host with a given target IP address by asking to
292 * host service. It does not support overlapping IP.
293 *
294 * @param targetIp target ip
295 * @return mac address, or NONE mac address if it fails to find the mac
296 */
297 private MacAddress getMacFromHostService(IpAddress targetIp) {
298 checkNotNull(targetIp);
299
300 Host host = hostService.getHostsByIp(targetIp)
301 .stream()
302 .findFirst()
303 .orElse(null);
304
305 if (host != null) {
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700306 log.trace("Found MAC from host service for {}", targetIp);
Hyunsun Moonb6febbe2016-02-12 15:59:53 -0800307 return host.mac();
308 } else {
309 return MacAddress.NONE;
310 }
311 }
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700312
313 private class InternalPacketProcessor implements PacketProcessor {
314
315 @Override
316 public void process(PacketContext context) {
317 if (context.isHandled()) {
318 return;
319 }
320 Ethernet ethPacket = context.inPacket().parsed();
321 if (ethPacket == null || ethPacket.getEtherType() != Ethernet.TYPE_ARP) {
322 return;
323 }
Hyunsun Moonc031d9b2016-08-04 13:57:22 -0700324
325 ARP arpPacket = (ARP) ethPacket.getPayload();
326 switch (arpPacket.getOpCode()) {
327 case ARP.OP_REQUEST:
328 processArpRequest(context, ethPacket);
329 break;
330 case ARP.OP_REPLY:
331 processArpReply(context, ethPacket);
332 break;
333 default:
334 break;
335 }
Hyunsun Moon5401aaa2016-06-12 17:40:34 -0700336 }
337 }
338
339 @Override
340 public void instanceDetected(Instance instance) {
341 VtnService service = getVtnService(instance.serviceId());
342 if (service == null) {
343 return;
344 }
345
346 if (service.networkType().equals(PRIVATE)) {
347 log.trace("Added IP:{} MAC:{}", service.serviceIp(), privateGatewayMac);
348 addGateway(service.serviceIp(), privateGatewayMac);
349 // send gratuitous ARP for the existing VMs when ONOS is restarted
350 sendGratuitousArp(service.serviceIp(), Sets.newHashSet(instance));
351 }
352 }
353
354 @Override
355 public void instanceRemoved(Instance instance) {
356 VtnService service = getVtnService(instance.serviceId());
357 if (service == null) {
358 return;
359 }
360
361 // remove this network gateway from proxy ARP if no instance presents
362 if (service.networkType().equals(PRIVATE) &&
363 getInstances(service.id()).isEmpty()) {
364 removeGateway(service.serviceIp());
365 }
366 }
367
368 @Override
369 protected void readConfiguration() {
370 CordVtnConfig config = configRegistry.getConfig(appId, CordVtnConfig.class);
371 if (config == null) {
372 log.debug("No configuration found");
373 return;
374 }
375
376 privateGatewayMac = config.privateGatewayMac();
377 log.debug("Added private gateway MAC {}", privateGatewayMac);
378
379 config.publicGateways().entrySet().stream().forEach(entry -> {
380 addGateway(entry.getKey(), entry.getValue());
381 log.debug("Added public gateway IP {}, MAC {}",
382 entry.getKey(), entry.getValue());
383 });
384 // TODO send gratuitous arp in case the MAC is changed
385
386 super.readConfiguration();
387 }
Hyunsun Moon022272f2016-01-11 15:30:42 -0800388}