blob: 90bc15e74d1c7be6541b084bba857a05add79f6c [file] [log] [blame]
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07001/*
2 * Copyright 2014 Open Networking Laboratory
3 *
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 */
Brian O'Connorabafb502014-12-02 22:26:20 -080016package org.onosproject.net.proxyarp.impl;
alshabibb5522ff2014-09-29 19:20:00 -070017
18import static com.google.common.base.Preconditions.checkArgument;
19import static com.google.common.base.Preconditions.checkNotNull;
20import static org.slf4j.LoggerFactory.getLogger;
21
22import java.nio.ByteBuffer;
Jonathan Hart1f793a72014-11-12 23:22:02 -080023import java.util.HashSet;
alshabibb5522ff2014-09-29 19:20:00 -070024import java.util.List;
25import java.util.Map.Entry;
26import java.util.Set;
27
28import org.apache.felix.scr.annotations.Activate;
29import org.apache.felix.scr.annotations.Component;
30import org.apache.felix.scr.annotations.Deactivate;
31import org.apache.felix.scr.annotations.Reference;
32import org.apache.felix.scr.annotations.ReferenceCardinality;
33import org.apache.felix.scr.annotations.Service;
Jonathan Harte8600eb2015-01-12 10:30:45 -080034import org.onlab.packet.ARP;
35import org.onlab.packet.Ethernet;
36import org.onlab.packet.Ip4Address;
37import org.onlab.packet.IpAddress;
38import org.onlab.packet.MacAddress;
39import org.onlab.packet.VlanId;
Brian O'Connorabafb502014-12-02 22:26:20 -080040import org.onosproject.net.ConnectPoint;
41import org.onosproject.net.Device;
42import org.onosproject.net.Host;
43import org.onosproject.net.HostId;
44import org.onosproject.net.Link;
45import org.onosproject.net.Port;
46import org.onosproject.net.PortNumber;
47import org.onosproject.net.device.DeviceEvent;
48import org.onosproject.net.device.DeviceListener;
49import org.onosproject.net.device.DeviceService;
50import org.onosproject.net.flow.DefaultTrafficTreatment;
51import org.onosproject.net.flow.TrafficTreatment;
52import org.onosproject.net.host.HostService;
53import org.onosproject.net.host.InterfaceIpAddress;
54import org.onosproject.net.host.PortAddresses;
55import org.onosproject.net.link.LinkEvent;
56import org.onosproject.net.link.LinkListener;
57import org.onosproject.net.link.LinkService;
58import org.onosproject.net.packet.DefaultOutboundPacket;
59import org.onosproject.net.packet.InboundPacket;
60import org.onosproject.net.packet.PacketContext;
61import org.onosproject.net.packet.PacketService;
62import org.onosproject.net.proxyarp.ProxyArpService;
alshabibb5522ff2014-09-29 19:20:00 -070063import org.slf4j.Logger;
64
65import com.google.common.collect.HashMultimap;
66import com.google.common.collect.Lists;
67import com.google.common.collect.Multimap;
68
alshabibb5522ff2014-09-29 19:20:00 -070069@Component(immediate = true)
70@Service
71public class ProxyArpManager implements ProxyArpService {
72
73 private final Logger log = getLogger(getClass());
74
75 private static final String MAC_ADDR_NULL = "Mac address cannot be null.";
76 private static final String REQUEST_NULL = "Arp request cannot be null.";
77 private static final String REQUEST_NOT_ARP = "Ethernet frame does not contain ARP request.";
78 private static final String NOT_ARP_REQUEST = "ARP is not a request.";
Jonathan Hart704ca142014-10-09 09:34:39 -070079 private static final String NOT_ARP_REPLY = "ARP is not a reply.";
alshabibb5522ff2014-09-29 19:20:00 -070080
81 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
82 protected HostService hostService;
83
84 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
85 protected PacketService packetService;
86
87 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
88 protected LinkService linkService;
89
90 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
91 protected DeviceService deviceService;
92
93 private final Multimap<Device, PortNumber> internalPorts =
94 HashMultimap.<Device, PortNumber>create();
95
96 private final Multimap<Device, PortNumber> externalPorts =
97 HashMultimap.<Device, PortNumber>create();
98
99 /**
100 * Listens to both device service and link service to determine
101 * whether a port is internal or external.
102 */
103 @Activate
104 public void activate() {
105 deviceService.addListener(new InternalDeviceListener());
106 linkService.addListener(new InternalLinkListener());
107 determinePortLocations();
Pavlin Radoslavovd36a74b2015-01-09 11:59:07 -0800108
alshabibb5522ff2014-09-29 19:20:00 -0700109 log.info("Started");
110 }
111
112
113 @Deactivate
114 public void deactivate() {
115 log.info("Stopped");
116 }
117
118 @Override
Jonathan Hartf84591d2015-01-16 14:33:43 -0800119 public boolean isKnown(Ip4Address addr) {
Yuta HIGUCHI59718042014-10-04 22:04:56 -0700120 checkNotNull(addr, MAC_ADDR_NULL);
alshabibb5522ff2014-09-29 19:20:00 -0700121 Set<Host> hosts = hostService.getHostsByIp(addr);
122 return !hosts.isEmpty();
123 }
124
125 @Override
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700126 public void reply(Ethernet eth, ConnectPoint inPort) {
Yuta HIGUCHI59718042014-10-04 22:04:56 -0700127 checkNotNull(eth, REQUEST_NULL);
alshabibb5522ff2014-09-29 19:20:00 -0700128 checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP,
129 REQUEST_NOT_ARP);
130 ARP arp = (ARP) eth.getPayload();
131 checkArgument(arp.getOpCode() == ARP.OP_REQUEST, NOT_ARP_REQUEST);
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700132 checkNotNull(inPort);
133
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700134 // If the request came from outside the network, only reply if it was
135 // for one of our external addresses.
136 if (isOutsidePort(inPort)) {
Pavlin Radoslavov5b5dc482014-11-05 14:48:08 -0800137 Ip4Address target =
138 Ip4Address.valueOf(arp.getTargetProtocolAddress());
Jonathan Harta887ba82014-11-03 15:20:52 -0800139 Set<PortAddresses> addressSet =
Pavlin Radoslavov76b0ae22014-10-27 15:33:19 -0700140 hostService.getAddressBindingsForPort(inPort);
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700141
Jonathan Harta887ba82014-11-03 15:20:52 -0800142 for (PortAddresses addresses : addressSet) {
143 for (InterfaceIpAddress ia : addresses.ipAddresses()) {
144 if (ia.ipAddress().equals(target)) {
145 Ethernet arpReply =
Pavlin Radoslavov5b5dc482014-11-05 14:48:08 -0800146 buildArpReply(target, addresses.mac(), eth);
Jonathan Harta887ba82014-11-03 15:20:52 -0800147 sendTo(arpReply, inPort);
148 }
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700149 }
150 }
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700151 return;
Pavlin Radoslavov76b0ae22014-10-27 15:33:19 -0700152 } else {
153 // If the source address matches one of our external addresses
154 // it could be a request from an internal host to an external
Jonathan Hart1f793a72014-11-12 23:22:02 -0800155 // address. Forward it over to the correct ports.
Pavlin Radoslavov5b5dc482014-11-05 14:48:08 -0800156 Ip4Address source =
157 Ip4Address.valueOf(arp.getSenderProtocolAddress());
Jonathan Hart1f793a72014-11-12 23:22:02 -0800158 Set<PortAddresses> sourceAddresses = findPortsInSubnet(source);
159 boolean matched = false;
160 for (PortAddresses pa : sourceAddresses) {
161 for (InterfaceIpAddress ia : pa.ipAddresses()) {
Pavlin Radoslavov76b0ae22014-10-27 15:33:19 -0700162 if (ia.ipAddress().equals(source)) {
Jonathan Hart1f793a72014-11-12 23:22:02 -0800163 matched = true;
164 sendTo(eth, pa.connectPoint());
Pavlin Radoslavov76b0ae22014-10-27 15:33:19 -0700165 }
166 }
167 }
Jonathan Hart1f793a72014-11-12 23:22:02 -0800168
169 if (matched) {
170 return;
171 }
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700172 }
173
174 // Continue with normal proxy ARP case
alshabibb5522ff2014-09-29 19:20:00 -0700175
176 VlanId vlan = VlanId.vlanId(eth.getVlanID());
Pavlin Radoslavov5b5dc482014-11-05 14:48:08 -0800177 Set<Host> hosts = hostService.getHostsByIp(
178 Ip4Address.valueOf(arp.getTargetProtocolAddress()));
alshabibb5522ff2014-09-29 19:20:00 -0700179
180 Host dst = null;
181 Host src = hostService.getHost(HostId.hostId(eth.getSourceMAC(),
182 VlanId.vlanId(eth.getVlanID())));
183
184 for (Host host : hosts) {
185 if (host.vlan().equals(vlan)) {
186 dst = host;
187 break;
188 }
189 }
190
191 if (src == null || dst == null) {
Jonathan Hartf84591d2015-01-16 14:33:43 -0800192 flood(eth, inPort);
alshabibb5522ff2014-09-29 19:20:00 -0700193 return;
194 }
195
Pavlin Radoslavov5b5dc482014-11-05 14:48:08 -0800196 //
197 // TODO find the correct IP address.
198 // Right now we use the first IPv4 address that is found.
199 //
200 for (IpAddress ipAddress : dst.ipAddresses()) {
201 Ip4Address ip4Address = ipAddress.getIp4Address();
202 if (ip4Address != null) {
203 Ethernet arpReply = buildArpReply(ip4Address, dst.mac(), eth);
204 // TODO: check send status with host service.
205 sendTo(arpReply, src.location());
206 break;
207 }
208 }
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700209 }
210
211 /**
212 * Outputs the given packet out the given port.
213 *
214 * @param packet the packet to send
215 * @param outPort the port to send it out
216 */
217 private void sendTo(Ethernet packet, ConnectPoint outPort) {
218 if (internalPorts.containsEntry(
219 deviceService.getDevice(outPort.deviceId()), outPort.port())) {
220 // Sanity check to make sure we don't send the packet out an
221 // internal port and create a loop (could happen due to
222 // misconfiguration).
223 return;
224 }
225
tom9a693fd2014-10-03 11:32:19 -0700226 TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700227 builder.setOutput(outPort.port());
228 packetService.emit(new DefaultOutboundPacket(outPort.deviceId(),
229 builder.build(), ByteBuffer.wrap(packet.serialize())));
230 }
231
232 /**
Jonathan Hart1f793a72014-11-12 23:22:02 -0800233 * Finds ports with an address in the subnet of the target address.
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700234 *
Pavlin Radoslavov76b0ae22014-10-27 15:33:19 -0700235 * @param target the target address to find a matching port for
Jonathan Hart1f793a72014-11-12 23:22:02 -0800236 * @return a set of PortAddresses describing ports in the subnet
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700237 */
Jonathan Hart1f793a72014-11-12 23:22:02 -0800238 private Set<PortAddresses> findPortsInSubnet(Ip4Address target) {
239 Set<PortAddresses> result = new HashSet<PortAddresses>();
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700240 for (PortAddresses addresses : hostService.getAddressBindings()) {
Pavlin Radoslavov76b0ae22014-10-27 15:33:19 -0700241 for (InterfaceIpAddress ia : addresses.ipAddresses()) {
242 if (ia.subnetAddress().contains(target)) {
Jonathan Hart1f793a72014-11-12 23:22:02 -0800243 result.add(addresses);
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700244 }
245 }
246 }
Jonathan Hart1f793a72014-11-12 23:22:02 -0800247 return result;
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700248 }
249
250 /**
251 * Returns whether the given port is an outside-facing port with an IP
252 * address configured.
253 *
254 * @param port the port to check
255 * @return true if the port is an outside-facing port, otherwise false
256 */
257 private boolean isOutsidePort(ConnectPoint port) {
Pavlin Radoslavov76b0ae22014-10-27 15:33:19 -0700258 //
259 // TODO: Is this sufficient to identify outside-facing ports: just
260 // having IP addresses on a port?
261 //
Jonathan Harta887ba82014-11-03 15:20:52 -0800262 return !hostService.getAddressBindingsForPort(port).isEmpty();
alshabibb5522ff2014-09-29 19:20:00 -0700263 }
264
265 @Override
Jonathan Hartf84591d2015-01-16 14:33:43 -0800266 public void forward(Ethernet eth, ConnectPoint inPort) {
Yuta HIGUCHI59718042014-10-04 22:04:56 -0700267 checkNotNull(eth, REQUEST_NULL);
alshabibb5522ff2014-09-29 19:20:00 -0700268 checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP,
269 REQUEST_NOT_ARP);
270 ARP arp = (ARP) eth.getPayload();
Jonathan Hart704ca142014-10-09 09:34:39 -0700271 checkArgument(arp.getOpCode() == ARP.OP_REPLY, NOT_ARP_REPLY);
alshabibb5522ff2014-09-29 19:20:00 -0700272
273 Host h = hostService.getHost(HostId.hostId(eth.getDestinationMAC(),
274 VlanId.vlanId(eth.getVlanID())));
275
276 if (h == null) {
Jonathan Hartf84591d2015-01-16 14:33:43 -0800277 flood(eth, inPort);
alshabibb5522ff2014-09-29 19:20:00 -0700278 } else {
tom9a693fd2014-10-03 11:32:19 -0700279 TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
alshabibb5522ff2014-09-29 19:20:00 -0700280 builder.setOutput(h.location().port());
281 packetService.emit(new DefaultOutboundPacket(h.location().deviceId(),
282 builder.build(), ByteBuffer.wrap(eth.serialize())));
283 }
284
285 }
286
alshabibc274c902014-10-03 14:58:27 -0700287 @Override
288 public boolean handleArp(PacketContext context) {
289 InboundPacket pkt = context.inPacket();
290 Ethernet ethPkt = pkt.parsed();
Jonathan Harte8600eb2015-01-12 10:30:45 -0800291 if (ethPkt != null && ethPkt.getEtherType() == Ethernet.TYPE_ARP) {
alshabibc274c902014-10-03 14:58:27 -0700292 ARP arp = (ARP) ethPkt.getPayload();
293 if (arp.getOpCode() == ARP.OP_REPLY) {
Jonathan Hartf84591d2015-01-16 14:33:43 -0800294 forward(ethPkt, context.inPacket().receivedFrom());
alshabibc274c902014-10-03 14:58:27 -0700295 } else if (arp.getOpCode() == ARP.OP_REQUEST) {
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700296 reply(ethPkt, context.inPacket().receivedFrom());
alshabibc274c902014-10-03 14:58:27 -0700297 }
298 context.block();
299 return true;
300 }
301 return false;
302 }
303
alshabibb5522ff2014-09-29 19:20:00 -0700304 /**
305 * Flood the arp request at all edges in the network.
306 * @param request the arp request.
307 */
Jonathan Hartf84591d2015-01-16 14:33:43 -0800308 private void flood(Ethernet request, ConnectPoint inPort) {
alshabibb5522ff2014-09-29 19:20:00 -0700309 TrafficTreatment.Builder builder = null;
310 ByteBuffer buf = ByteBuffer.wrap(request.serialize());
311
312 synchronized (externalPorts) {
313 for (Entry<Device, PortNumber> entry : externalPorts.entries()) {
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700314 ConnectPoint cp = new ConnectPoint(entry.getKey().id(), entry.getValue());
Jonathan Hartf84591d2015-01-16 14:33:43 -0800315 if (isOutsidePort(cp) || cp.equals(inPort)) {
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700316 continue;
317 }
318
tom9a693fd2014-10-03 11:32:19 -0700319 builder = DefaultTrafficTreatment.builder();
alshabibb5522ff2014-09-29 19:20:00 -0700320 builder.setOutput(entry.getValue());
321 packetService.emit(new DefaultOutboundPacket(entry.getKey().id(),
322 builder.build(), buf));
323 }
alshabibb5522ff2014-09-29 19:20:00 -0700324 }
325 }
326
327 /**
328 * Determines the location of all known ports in the system.
329 */
330 private void determinePortLocations() {
331 Iterable<Device> devices = deviceService.getDevices();
332 Iterable<Link> links = null;
333 List<PortNumber> ports = null;
334 for (Device d : devices) {
335 ports = buildPortNumberList(deviceService.getPorts(d.id()));
336 links = linkService.getLinks();
337 for (Link l : links) {
338 // for each link, mark the concerned ports as internal
339 // and the remaining ports are therefore external.
Yuta HIGUCHI3541bf22014-10-04 22:06:19 -0700340 if (l.src().deviceId().equals(d.id())
alshabibb5522ff2014-09-29 19:20:00 -0700341 && ports.contains(l.src().port())) {
342 ports.remove(l.src().port());
343 internalPorts.put(d, l.src().port());
344 }
Yuta HIGUCHI3541bf22014-10-04 22:06:19 -0700345 if (l.dst().deviceId().equals(d.id())
alshabibb5522ff2014-09-29 19:20:00 -0700346 && ports.contains(l.dst().port())) {
347 ports.remove(l.dst().port());
348 internalPorts.put(d, l.dst().port());
349 }
350 }
351 synchronized (externalPorts) {
352 externalPorts.putAll(d, ports);
353 }
354 }
355
356 }
357
358 private List<PortNumber> buildPortNumberList(List<Port> ports) {
359 List<PortNumber> portNumbers = Lists.newLinkedList();
360 for (Port p : ports) {
361 portNumbers.add(p.number());
362 }
363 return portNumbers;
364 }
365
366 /**
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700367 * Builds an ARP reply based on a request.
368 *
369 * @param srcIp the IP address to use as the reply source
370 * @param srcMac the MAC address to use as the reply source
371 * @param request the ARP request we got
372 * @return an Ethernet frame containing the ARP reply
alshabibb5522ff2014-09-29 19:20:00 -0700373 */
Pavlin Radoslavov5b5dc482014-11-05 14:48:08 -0800374 private Ethernet buildArpReply(Ip4Address srcIp, MacAddress srcMac,
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700375 Ethernet request) {
376
alshabibb5522ff2014-09-29 19:20:00 -0700377 Ethernet eth = new Ethernet();
Yuta HIGUCHI3e848a82014-11-02 20:19:42 -0800378 eth.setDestinationMACAddress(request.getSourceMAC());
379 eth.setSourceMACAddress(srcMac);
alshabibb5522ff2014-09-29 19:20:00 -0700380 eth.setEtherType(Ethernet.TYPE_ARP);
381 eth.setVlanID(request.getVlanID());
382
383 ARP arp = new ARP();
384 arp.setOpCode(ARP.OP_REPLY);
385 arp.setProtocolType(ARP.PROTO_TYPE_IP);
386 arp.setHardwareType(ARP.HW_TYPE_ETHERNET);
alshabib6eb438a2014-10-01 16:39:37 -0700387
Pavlin Radoslavov5b5dc482014-11-05 14:48:08 -0800388 arp.setProtocolAddressLength((byte) Ip4Address.BYTE_LENGTH);
alshabibb5522ff2014-09-29 19:20:00 -0700389 arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
Yuta HIGUCHI3e848a82014-11-02 20:19:42 -0800390 arp.setSenderHardwareAddress(srcMac.toBytes());
alshabibb5522ff2014-09-29 19:20:00 -0700391 arp.setTargetHardwareAddress(request.getSourceMACAddress());
392
393 arp.setTargetProtocolAddress(((ARP) request.getPayload())
394 .getSenderProtocolAddress());
Jonathan Hartbcae7bd2014-10-16 10:24:41 -0700395 arp.setSenderProtocolAddress(srcIp.toInt());
alshabibb5522ff2014-09-29 19:20:00 -0700396 eth.setPayload(arp);
397 return eth;
398 }
399
400 public class InternalLinkListener implements LinkListener {
401
402 @Override
403 public void event(LinkEvent event) {
404 Link link = event.subject();
405 Device src = deviceService.getDevice(link.src().deviceId());
406 Device dst = deviceService.getDevice(link.dst().deviceId());
407 switch (event.type()) {
408 case LINK_ADDED:
409 synchronized (externalPorts) {
410 externalPorts.remove(src, link.src().port());
411 externalPorts.remove(dst, link.dst().port());
412 internalPorts.put(src, link.src().port());
413 internalPorts.put(dst, link.dst().port());
414 }
415
416 break;
417 case LINK_REMOVED:
418 synchronized (externalPorts) {
419 externalPorts.put(src, link.src().port());
420 externalPorts.put(dst, link.dst().port());
421 internalPorts.remove(src, link.src().port());
422 internalPorts.remove(dst, link.dst().port());
423 }
424
425 break;
426 case LINK_UPDATED:
427 // don't care about links being updated.
428 break;
429 default:
430 break;
431 }
432
433 }
434
435 }
436
437 public class InternalDeviceListener implements DeviceListener {
438
439 @Override
440 public void event(DeviceEvent event) {
441 Device device = event.subject();
442 switch (event.type()) {
443 case DEVICE_ADDED:
444 case DEVICE_AVAILABILITY_CHANGED:
alshabibb5522ff2014-09-29 19:20:00 -0700445 case DEVICE_SUSPENDED:
446 case DEVICE_UPDATED:
alshabibb5522ff2014-09-29 19:20:00 -0700447 // nothing to do in these cases; handled when links get reported
448 break;
449 case DEVICE_REMOVED:
450 synchronized (externalPorts) {
451 externalPorts.removeAll(device);
452 internalPorts.removeAll(device);
453 }
454 break;
455 case PORT_ADDED:
alshabib6eb438a2014-10-01 16:39:37 -0700456 case PORT_UPDATED:
alshabibb5522ff2014-09-29 19:20:00 -0700457 synchronized (externalPorts) {
alshabib6eb438a2014-10-01 16:39:37 -0700458 if (event.port().isEnabled()) {
459 externalPorts.put(device, event.port().number());
460 internalPorts.remove(device, event.port().number());
461 }
alshabibb5522ff2014-09-29 19:20:00 -0700462 }
463 break;
464 case PORT_REMOVED:
465 synchronized (externalPorts) {
466 externalPorts.remove(device, event.port().number());
467 internalPorts.remove(device, event.port().number());
468 }
469 break;
470 default:
471 break;
472
473 }
474
475 }
476
alshabibc274c902014-10-03 14:58:27 -0700477 }
alshabibb5522ff2014-09-29 19:20:00 -0700478
479}