blob: 81f42c8139be26d21daf3c33d0fef38d9d579187 [file] [log] [blame]
alshabibb5522ff2014-09-29 19:20:00 -07001package org.onlab.onos.net.proxyarp.impl;
2
3import static com.google.common.base.Preconditions.checkArgument;
4import static com.google.common.base.Preconditions.checkNotNull;
5import static org.slf4j.LoggerFactory.getLogger;
6
7import java.nio.ByteBuffer;
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -07008import java.util.Collections;
alshabibb5522ff2014-09-29 19:20:00 -07009import java.util.List;
10import java.util.Map.Entry;
11import java.util.Set;
12
13import org.apache.felix.scr.annotations.Activate;
14import org.apache.felix.scr.annotations.Component;
15import org.apache.felix.scr.annotations.Deactivate;
16import org.apache.felix.scr.annotations.Reference;
17import org.apache.felix.scr.annotations.ReferenceCardinality;
18import org.apache.felix.scr.annotations.Service;
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -070019import org.onlab.onos.net.ConnectPoint;
alshabibb5522ff2014-09-29 19:20:00 -070020import org.onlab.onos.net.Device;
21import org.onlab.onos.net.Host;
22import org.onlab.onos.net.HostId;
23import org.onlab.onos.net.Link;
24import org.onlab.onos.net.Port;
25import org.onlab.onos.net.PortNumber;
26import org.onlab.onos.net.device.DeviceEvent;
27import org.onlab.onos.net.device.DeviceListener;
28import org.onlab.onos.net.device.DeviceService;
29import org.onlab.onos.net.flow.DefaultTrafficTreatment;
30import org.onlab.onos.net.flow.TrafficTreatment;
31import org.onlab.onos.net.host.HostService;
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -070032import org.onlab.onos.net.host.PortAddresses;
alshabibb5522ff2014-09-29 19:20:00 -070033import org.onlab.onos.net.link.LinkEvent;
34import org.onlab.onos.net.link.LinkListener;
35import org.onlab.onos.net.link.LinkService;
36import org.onlab.onos.net.packet.DefaultOutboundPacket;
alshabibc274c902014-10-03 14:58:27 -070037import org.onlab.onos.net.packet.InboundPacket;
38import org.onlab.onos.net.packet.PacketContext;
alshabibb5522ff2014-09-29 19:20:00 -070039import org.onlab.onos.net.packet.PacketService;
40import org.onlab.onos.net.proxyarp.ProxyArpService;
41import org.onlab.packet.ARP;
42import org.onlab.packet.Ethernet;
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -070043import org.onlab.packet.IpAddress;
alshabibb5522ff2014-09-29 19:20:00 -070044import org.onlab.packet.IpPrefix;
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -070045import org.onlab.packet.MacAddress;
alshabibb5522ff2014-09-29 19:20:00 -070046import org.onlab.packet.VlanId;
47import org.slf4j.Logger;
48
49import com.google.common.collect.HashMultimap;
50import com.google.common.collect.Lists;
51import com.google.common.collect.Multimap;
52
alshabibb5522ff2014-09-29 19:20:00 -070053@Component(immediate = true)
54@Service
55public class ProxyArpManager implements ProxyArpService {
56
57 private final Logger log = getLogger(getClass());
58
59 private static final String MAC_ADDR_NULL = "Mac address cannot be null.";
60 private static final String REQUEST_NULL = "Arp request cannot be null.";
61 private static final String REQUEST_NOT_ARP = "Ethernet frame does not contain ARP request.";
62 private static final String NOT_ARP_REQUEST = "ARP is not a request.";
Jonathan Hart704ca142014-10-09 09:34:39 -070063 private static final String NOT_ARP_REPLY = "ARP is not a reply.";
alshabibb5522ff2014-09-29 19:20:00 -070064
65 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
66 protected HostService hostService;
67
68 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
69 protected PacketService packetService;
70
71 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
72 protected LinkService linkService;
73
74 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
75 protected DeviceService deviceService;
76
77 private final Multimap<Device, PortNumber> internalPorts =
78 HashMultimap.<Device, PortNumber>create();
79
80 private final Multimap<Device, PortNumber> externalPorts =
81 HashMultimap.<Device, PortNumber>create();
82
83 /**
84 * Listens to both device service and link service to determine
85 * whether a port is internal or external.
86 */
87 @Activate
88 public void activate() {
89 deviceService.addListener(new InternalDeviceListener());
90 linkService.addListener(new InternalLinkListener());
91 determinePortLocations();
92 log.info("Started");
93 }
94
95
96 @Deactivate
97 public void deactivate() {
98 log.info("Stopped");
99 }
100
101 @Override
102 public boolean known(IpPrefix addr) {
Yuta HIGUCHI59718042014-10-04 22:04:56 -0700103 checkNotNull(addr, MAC_ADDR_NULL);
alshabibb5522ff2014-09-29 19:20:00 -0700104 Set<Host> hosts = hostService.getHostsByIp(addr);
105 return !hosts.isEmpty();
106 }
107
108 @Override
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700109 public void reply(Ethernet eth, ConnectPoint inPort) {
Yuta HIGUCHI59718042014-10-04 22:04:56 -0700110 checkNotNull(eth, REQUEST_NULL);
alshabibb5522ff2014-09-29 19:20:00 -0700111 checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP,
112 REQUEST_NOT_ARP);
113 ARP arp = (ARP) eth.getPayload();
114 checkArgument(arp.getOpCode() == ARP.OP_REQUEST, NOT_ARP_REQUEST);
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700115 checkNotNull(inPort);
116
117 // If the source address matches one of our external addresses
118 // it could be a request from an internal host to an external
119 // address. Forward it over to the correct port.
120 IpAddress source = IpAddress.valueOf(arp.getSenderProtocolAddress());
121 PortAddresses sourceAddresses = findOutsidePortInSubnet(source);
122 if (sourceAddresses != null && !isOutsidePort(inPort)) {
123 for (IpPrefix subnet : sourceAddresses.ips()) {
124 if (subnet.toIpAddress().equals(source)) {
125 sendTo(eth, sourceAddresses.connectPoint());
126 return;
127 }
128 }
129 }
130
131 // If the request came from outside the network, only reply if it was
132 // for one of our external addresses.
133 if (isOutsidePort(inPort)) {
134 IpAddress target = IpAddress.valueOf(arp.getTargetProtocolAddress());
135 PortAddresses addresses = hostService.getAddressBindingsForPort(inPort);
136
137 for (IpPrefix interfaceAddress : addresses.ips()) {
138 if (interfaceAddress.toIpAddress().equals(target)) {
139 Ethernet arpReply = buildArpReply(interfaceAddress,
140 addresses.mac(), eth);
141 sendTo(arpReply, inPort);
142 }
143 }
144
145 return;
146 }
147
148 // Continue with normal proxy ARP case
alshabibb5522ff2014-09-29 19:20:00 -0700149
150 VlanId vlan = VlanId.vlanId(eth.getVlanID());
151 Set<Host> hosts = hostService.getHostsByIp(IpPrefix.valueOf(arp
152 .getTargetProtocolAddress()));
153
154 Host dst = null;
155 Host src = hostService.getHost(HostId.hostId(eth.getSourceMAC(),
156 VlanId.vlanId(eth.getVlanID())));
157
158 for (Host host : hosts) {
159 if (host.vlan().equals(vlan)) {
160 dst = host;
161 break;
162 }
163 }
164
165 if (src == null || dst == null) {
166 flood(eth);
167 return;
168 }
169
Jonathan Hart7d1ad602014-10-17 11:48:32 -0700170 // TODO find the correct IP address
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700171 Ethernet arpReply = buildArpReply(dst.ipAddresses().iterator().next(),
172 dst.mac(), eth);
alshabibb5522ff2014-09-29 19:20:00 -0700173 // TODO: check send status with host service.
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700174 sendTo(arpReply, src.location());
175 }
176
177 /**
178 * Outputs the given packet out the given port.
179 *
180 * @param packet the packet to send
181 * @param outPort the port to send it out
182 */
183 private void sendTo(Ethernet packet, ConnectPoint outPort) {
184 if (internalPorts.containsEntry(
185 deviceService.getDevice(outPort.deviceId()), outPort.port())) {
186 // Sanity check to make sure we don't send the packet out an
187 // internal port and create a loop (could happen due to
188 // misconfiguration).
189 return;
190 }
191
tom9a693fd2014-10-03 11:32:19 -0700192 TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700193 builder.setOutput(outPort.port());
194 packetService.emit(new DefaultOutboundPacket(outPort.deviceId(),
195 builder.build(), ByteBuffer.wrap(packet.serialize())));
196 }
197
198 /**
199 * Finds the port with an address in the subnet of the target address, if
200 * one exists.
201 *
202 * @param target the target address to find a matching external port for
203 * @return a PortAddresses object containing the external addresses if one
204 * was found, otherwise null.
205 */
206 private PortAddresses findOutsidePortInSubnet(IpAddress target) {
207 for (PortAddresses addresses : hostService.getAddressBindings()) {
208 for (IpPrefix prefix : addresses.ips()) {
209 if (prefix.contains(target)) {
210 return new PortAddresses(addresses.connectPoint(),
211 Collections.singleton(prefix), addresses.mac());
212 }
213 }
214 }
215 return null;
216 }
217
218 /**
219 * Returns whether the given port is an outside-facing port with an IP
220 * address configured.
221 *
222 * @param port the port to check
223 * @return true if the port is an outside-facing port, otherwise false
224 */
225 private boolean isOutsidePort(ConnectPoint port) {
226 return !hostService.getAddressBindingsForPort(port).ips().isEmpty();
alshabibb5522ff2014-09-29 19:20:00 -0700227 }
228
229 @Override
230 public void forward(Ethernet eth) {
Yuta HIGUCHI59718042014-10-04 22:04:56 -0700231 checkNotNull(eth, REQUEST_NULL);
alshabibb5522ff2014-09-29 19:20:00 -0700232 checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP,
233 REQUEST_NOT_ARP);
234 ARP arp = (ARP) eth.getPayload();
Jonathan Hart704ca142014-10-09 09:34:39 -0700235 checkArgument(arp.getOpCode() == ARP.OP_REPLY, NOT_ARP_REPLY);
alshabibb5522ff2014-09-29 19:20:00 -0700236
237 Host h = hostService.getHost(HostId.hostId(eth.getDestinationMAC(),
238 VlanId.vlanId(eth.getVlanID())));
239
240 if (h == null) {
241 flood(eth);
242 } else {
tom9a693fd2014-10-03 11:32:19 -0700243 TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
alshabibb5522ff2014-09-29 19:20:00 -0700244 builder.setOutput(h.location().port());
245 packetService.emit(new DefaultOutboundPacket(h.location().deviceId(),
246 builder.build(), ByteBuffer.wrap(eth.serialize())));
247 }
248
249 }
250
alshabibc274c902014-10-03 14:58:27 -0700251 @Override
252 public boolean handleArp(PacketContext context) {
253 InboundPacket pkt = context.inPacket();
254 Ethernet ethPkt = pkt.parsed();
255 if (ethPkt.getEtherType() == Ethernet.TYPE_ARP) {
256 ARP arp = (ARP) ethPkt.getPayload();
257 if (arp.getOpCode() == ARP.OP_REPLY) {
258 forward(ethPkt);
259 } else if (arp.getOpCode() == ARP.OP_REQUEST) {
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700260 reply(ethPkt, context.inPacket().receivedFrom());
alshabibc274c902014-10-03 14:58:27 -0700261 }
262 context.block();
263 return true;
264 }
265 return false;
266 }
267
alshabibb5522ff2014-09-29 19:20:00 -0700268 /**
269 * Flood the arp request at all edges in the network.
270 * @param request the arp request.
271 */
272 private void flood(Ethernet request) {
273 TrafficTreatment.Builder builder = null;
274 ByteBuffer buf = ByteBuffer.wrap(request.serialize());
275
276 synchronized (externalPorts) {
277 for (Entry<Device, PortNumber> entry : externalPorts.entries()) {
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700278 ConnectPoint cp = new ConnectPoint(entry.getKey().id(), entry.getValue());
279 if (isOutsidePort(cp)) {
280 continue;
281 }
282
tom9a693fd2014-10-03 11:32:19 -0700283 builder = DefaultTrafficTreatment.builder();
alshabibb5522ff2014-09-29 19:20:00 -0700284 builder.setOutput(entry.getValue());
285 packetService.emit(new DefaultOutboundPacket(entry.getKey().id(),
286 builder.build(), buf));
287 }
alshabibb5522ff2014-09-29 19:20:00 -0700288 }
289 }
290
291 /**
292 * Determines the location of all known ports in the system.
293 */
294 private void determinePortLocations() {
295 Iterable<Device> devices = deviceService.getDevices();
296 Iterable<Link> links = null;
297 List<PortNumber> ports = null;
298 for (Device d : devices) {
299 ports = buildPortNumberList(deviceService.getPorts(d.id()));
300 links = linkService.getLinks();
301 for (Link l : links) {
302 // for each link, mark the concerned ports as internal
303 // and the remaining ports are therefore external.
Yuta HIGUCHI3541bf22014-10-04 22:06:19 -0700304 if (l.src().deviceId().equals(d.id())
alshabibb5522ff2014-09-29 19:20:00 -0700305 && ports.contains(l.src().port())) {
306 ports.remove(l.src().port());
307 internalPorts.put(d, l.src().port());
308 }
Yuta HIGUCHI3541bf22014-10-04 22:06:19 -0700309 if (l.dst().deviceId().equals(d.id())
alshabibb5522ff2014-09-29 19:20:00 -0700310 && ports.contains(l.dst().port())) {
311 ports.remove(l.dst().port());
312 internalPorts.put(d, l.dst().port());
313 }
314 }
315 synchronized (externalPorts) {
316 externalPorts.putAll(d, ports);
317 }
318 }
319
320 }
321
322 private List<PortNumber> buildPortNumberList(List<Port> ports) {
323 List<PortNumber> portNumbers = Lists.newLinkedList();
324 for (Port p : ports) {
325 portNumbers.add(p.number());
326 }
327 return portNumbers;
328 }
329
330 /**
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700331 * Builds an ARP reply based on a request.
332 *
333 * @param srcIp the IP address to use as the reply source
334 * @param srcMac the MAC address to use as the reply source
335 * @param request the ARP request we got
336 * @return an Ethernet frame containing the ARP reply
alshabibb5522ff2014-09-29 19:20:00 -0700337 */
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700338 private Ethernet buildArpReply(IpPrefix srcIp, MacAddress srcMac,
339 Ethernet request) {
340
alshabibb5522ff2014-09-29 19:20:00 -0700341 Ethernet eth = new Ethernet();
342 eth.setDestinationMACAddress(request.getSourceMACAddress());
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700343 eth.setSourceMACAddress(srcMac.getAddress());
alshabibb5522ff2014-09-29 19:20:00 -0700344 eth.setEtherType(Ethernet.TYPE_ARP);
345 eth.setVlanID(request.getVlanID());
346
347 ARP arp = new ARP();
348 arp.setOpCode(ARP.OP_REPLY);
349 arp.setProtocolType(ARP.PROTO_TYPE_IP);
350 arp.setHardwareType(ARP.HW_TYPE_ETHERNET);
alshabib6eb438a2014-10-01 16:39:37 -0700351
alshabibb5522ff2014-09-29 19:20:00 -0700352 arp.setProtocolAddressLength((byte) IpPrefix.INET_LEN);
353 arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700354 arp.setSenderHardwareAddress(srcMac.getAddress());
alshabibb5522ff2014-09-29 19:20:00 -0700355 arp.setTargetHardwareAddress(request.getSourceMACAddress());
356
357 arp.setTargetProtocolAddress(((ARP) request.getPayload())
358 .getSenderProtocolAddress());
Jonathan Hartbcae7bd2014-10-16 10:24:41 -0700359 arp.setSenderProtocolAddress(srcIp.toInt());
alshabibb5522ff2014-09-29 19:20:00 -0700360 eth.setPayload(arp);
361 return eth;
362 }
363
364 public class InternalLinkListener implements LinkListener {
365
366 @Override
367 public void event(LinkEvent event) {
368 Link link = event.subject();
369 Device src = deviceService.getDevice(link.src().deviceId());
370 Device dst = deviceService.getDevice(link.dst().deviceId());
371 switch (event.type()) {
372 case LINK_ADDED:
373 synchronized (externalPorts) {
374 externalPorts.remove(src, link.src().port());
375 externalPorts.remove(dst, link.dst().port());
376 internalPorts.put(src, link.src().port());
377 internalPorts.put(dst, link.dst().port());
378 }
379
380 break;
381 case LINK_REMOVED:
382 synchronized (externalPorts) {
383 externalPorts.put(src, link.src().port());
384 externalPorts.put(dst, link.dst().port());
385 internalPorts.remove(src, link.src().port());
386 internalPorts.remove(dst, link.dst().port());
387 }
388
389 break;
390 case LINK_UPDATED:
391 // don't care about links being updated.
392 break;
393 default:
394 break;
395 }
396
397 }
398
399 }
400
401 public class InternalDeviceListener implements DeviceListener {
402
403 @Override
404 public void event(DeviceEvent event) {
405 Device device = event.subject();
406 switch (event.type()) {
407 case DEVICE_ADDED:
408 case DEVICE_AVAILABILITY_CHANGED:
409 case DEVICE_MASTERSHIP_CHANGED:
410 case DEVICE_SUSPENDED:
411 case DEVICE_UPDATED:
alshabibb5522ff2014-09-29 19:20:00 -0700412 // nothing to do in these cases; handled when links get reported
413 break;
414 case DEVICE_REMOVED:
415 synchronized (externalPorts) {
416 externalPorts.removeAll(device);
417 internalPorts.removeAll(device);
418 }
419 break;
420 case PORT_ADDED:
alshabib6eb438a2014-10-01 16:39:37 -0700421 case PORT_UPDATED:
alshabibb5522ff2014-09-29 19:20:00 -0700422 synchronized (externalPorts) {
alshabib6eb438a2014-10-01 16:39:37 -0700423 if (event.port().isEnabled()) {
424 externalPorts.put(device, event.port().number());
425 internalPorts.remove(device, event.port().number());
426 }
alshabibb5522ff2014-09-29 19:20:00 -0700427 }
428 break;
429 case PORT_REMOVED:
430 synchronized (externalPorts) {
431 externalPorts.remove(device, event.port().number());
432 internalPorts.remove(device, event.port().number());
433 }
434 break;
435 default:
436 break;
437
438 }
439
440 }
441
alshabibc274c902014-10-03 14:58:27 -0700442 }
alshabibb5522ff2014-09-29 19:20:00 -0700443
444}