blob: cd84dcee59d57e67d10f339c3852f02dc9d8b8c3 [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 Hartdbdbdbb2014-10-06 18:35:30 -0700170 Ethernet arpReply = buildArpReply(dst.ipAddresses().iterator().next(),
171 dst.mac(), eth);
alshabibb5522ff2014-09-29 19:20:00 -0700172 // TODO: check send status with host service.
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700173 sendTo(arpReply, src.location());
174 }
175
176 /**
177 * Outputs the given packet out the given port.
178 *
179 * @param packet the packet to send
180 * @param outPort the port to send it out
181 */
182 private void sendTo(Ethernet packet, ConnectPoint outPort) {
183 if (internalPorts.containsEntry(
184 deviceService.getDevice(outPort.deviceId()), outPort.port())) {
185 // Sanity check to make sure we don't send the packet out an
186 // internal port and create a loop (could happen due to
187 // misconfiguration).
188 return;
189 }
190
tom9a693fd2014-10-03 11:32:19 -0700191 TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700192 builder.setOutput(outPort.port());
193 packetService.emit(new DefaultOutboundPacket(outPort.deviceId(),
194 builder.build(), ByteBuffer.wrap(packet.serialize())));
195 }
196
197 /**
198 * Finds the port with an address in the subnet of the target address, if
199 * one exists.
200 *
201 * @param target the target address to find a matching external port for
202 * @return a PortAddresses object containing the external addresses if one
203 * was found, otherwise null.
204 */
205 private PortAddresses findOutsidePortInSubnet(IpAddress target) {
206 for (PortAddresses addresses : hostService.getAddressBindings()) {
207 for (IpPrefix prefix : addresses.ips()) {
208 if (prefix.contains(target)) {
209 return new PortAddresses(addresses.connectPoint(),
210 Collections.singleton(prefix), addresses.mac());
211 }
212 }
213 }
214 return null;
215 }
216
217 /**
218 * Returns whether the given port is an outside-facing port with an IP
219 * address configured.
220 *
221 * @param port the port to check
222 * @return true if the port is an outside-facing port, otherwise false
223 */
224 private boolean isOutsidePort(ConnectPoint port) {
225 return !hostService.getAddressBindingsForPort(port).ips().isEmpty();
alshabibb5522ff2014-09-29 19:20:00 -0700226 }
227
228 @Override
229 public void forward(Ethernet eth) {
Yuta HIGUCHI59718042014-10-04 22:04:56 -0700230 checkNotNull(eth, REQUEST_NULL);
alshabibb5522ff2014-09-29 19:20:00 -0700231 checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP,
232 REQUEST_NOT_ARP);
233 ARP arp = (ARP) eth.getPayload();
Jonathan Hart704ca142014-10-09 09:34:39 -0700234 checkArgument(arp.getOpCode() == ARP.OP_REPLY, NOT_ARP_REPLY);
alshabibb5522ff2014-09-29 19:20:00 -0700235
236 Host h = hostService.getHost(HostId.hostId(eth.getDestinationMAC(),
237 VlanId.vlanId(eth.getVlanID())));
238
239 if (h == null) {
240 flood(eth);
241 } else {
tom9a693fd2014-10-03 11:32:19 -0700242 TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder();
alshabibb5522ff2014-09-29 19:20:00 -0700243 builder.setOutput(h.location().port());
244 packetService.emit(new DefaultOutboundPacket(h.location().deviceId(),
245 builder.build(), ByteBuffer.wrap(eth.serialize())));
246 }
247
248 }
249
alshabibc274c902014-10-03 14:58:27 -0700250 @Override
251 public boolean handleArp(PacketContext context) {
252 InboundPacket pkt = context.inPacket();
253 Ethernet ethPkt = pkt.parsed();
254 if (ethPkt.getEtherType() == Ethernet.TYPE_ARP) {
255 ARP arp = (ARP) ethPkt.getPayload();
256 if (arp.getOpCode() == ARP.OP_REPLY) {
257 forward(ethPkt);
258 } else if (arp.getOpCode() == ARP.OP_REQUEST) {
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700259 reply(ethPkt, context.inPacket().receivedFrom());
alshabibc274c902014-10-03 14:58:27 -0700260 }
261 context.block();
262 return true;
263 }
264 return false;
265 }
266
alshabibb5522ff2014-09-29 19:20:00 -0700267 /**
268 * Flood the arp request at all edges in the network.
269 * @param request the arp request.
270 */
271 private void flood(Ethernet request) {
272 TrafficTreatment.Builder builder = null;
273 ByteBuffer buf = ByteBuffer.wrap(request.serialize());
274
275 synchronized (externalPorts) {
276 for (Entry<Device, PortNumber> entry : externalPorts.entries()) {
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700277 ConnectPoint cp = new ConnectPoint(entry.getKey().id(), entry.getValue());
278 if (isOutsidePort(cp)) {
279 continue;
280 }
281
tom9a693fd2014-10-03 11:32:19 -0700282 builder = DefaultTrafficTreatment.builder();
alshabibb5522ff2014-09-29 19:20:00 -0700283 builder.setOutput(entry.getValue());
284 packetService.emit(new DefaultOutboundPacket(entry.getKey().id(),
285 builder.build(), buf));
286 }
alshabibb5522ff2014-09-29 19:20:00 -0700287 }
288 }
289
290 /**
291 * Determines the location of all known ports in the system.
292 */
293 private void determinePortLocations() {
294 Iterable<Device> devices = deviceService.getDevices();
295 Iterable<Link> links = null;
296 List<PortNumber> ports = null;
297 for (Device d : devices) {
298 ports = buildPortNumberList(deviceService.getPorts(d.id()));
299 links = linkService.getLinks();
300 for (Link l : links) {
301 // for each link, mark the concerned ports as internal
302 // and the remaining ports are therefore external.
Yuta HIGUCHI3541bf22014-10-04 22:06:19 -0700303 if (l.src().deviceId().equals(d.id())
alshabibb5522ff2014-09-29 19:20:00 -0700304 && ports.contains(l.src().port())) {
305 ports.remove(l.src().port());
306 internalPorts.put(d, l.src().port());
307 }
Yuta HIGUCHI3541bf22014-10-04 22:06:19 -0700308 if (l.dst().deviceId().equals(d.id())
alshabibb5522ff2014-09-29 19:20:00 -0700309 && ports.contains(l.dst().port())) {
310 ports.remove(l.dst().port());
311 internalPorts.put(d, l.dst().port());
312 }
313 }
314 synchronized (externalPorts) {
315 externalPorts.putAll(d, ports);
316 }
317 }
318
319 }
320
321 private List<PortNumber> buildPortNumberList(List<Port> ports) {
322 List<PortNumber> portNumbers = Lists.newLinkedList();
323 for (Port p : ports) {
324 portNumbers.add(p.number());
325 }
326 return portNumbers;
327 }
328
329 /**
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700330 * Builds an ARP reply based on a request.
331 *
332 * @param srcIp the IP address to use as the reply source
333 * @param srcMac the MAC address to use as the reply source
334 * @param request the ARP request we got
335 * @return an Ethernet frame containing the ARP reply
alshabibb5522ff2014-09-29 19:20:00 -0700336 */
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700337 private Ethernet buildArpReply(IpPrefix srcIp, MacAddress srcMac,
338 Ethernet request) {
339
alshabibb5522ff2014-09-29 19:20:00 -0700340 Ethernet eth = new Ethernet();
341 eth.setDestinationMACAddress(request.getSourceMACAddress());
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700342 eth.setSourceMACAddress(srcMac.getAddress());
alshabibb5522ff2014-09-29 19:20:00 -0700343 eth.setEtherType(Ethernet.TYPE_ARP);
344 eth.setVlanID(request.getVlanID());
345
346 ARP arp = new ARP();
347 arp.setOpCode(ARP.OP_REPLY);
348 arp.setProtocolType(ARP.PROTO_TYPE_IP);
349 arp.setHardwareType(ARP.HW_TYPE_ETHERNET);
alshabib6eb438a2014-10-01 16:39:37 -0700350
alshabibb5522ff2014-09-29 19:20:00 -0700351 arp.setProtocolAddressLength((byte) IpPrefix.INET_LEN);
352 arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH);
Jonathan Hartdbdbdbb2014-10-06 18:35:30 -0700353 arp.setSenderHardwareAddress(srcMac.getAddress());
alshabibb5522ff2014-09-29 19:20:00 -0700354 arp.setTargetHardwareAddress(request.getSourceMACAddress());
355
356 arp.setTargetProtocolAddress(((ARP) request.getPayload())
357 .getSenderProtocolAddress());
Jonathan Hartbcae7bd2014-10-16 10:24:41 -0700358 arp.setSenderProtocolAddress(srcIp.toInt());
alshabibb5522ff2014-09-29 19:20:00 -0700359 eth.setPayload(arp);
360 return eth;
361 }
362
363 public class InternalLinkListener implements LinkListener {
364
365 @Override
366 public void event(LinkEvent event) {
367 Link link = event.subject();
368 Device src = deviceService.getDevice(link.src().deviceId());
369 Device dst = deviceService.getDevice(link.dst().deviceId());
370 switch (event.type()) {
371 case LINK_ADDED:
372 synchronized (externalPorts) {
373 externalPorts.remove(src, link.src().port());
374 externalPorts.remove(dst, link.dst().port());
375 internalPorts.put(src, link.src().port());
376 internalPorts.put(dst, link.dst().port());
377 }
378
379 break;
380 case LINK_REMOVED:
381 synchronized (externalPorts) {
382 externalPorts.put(src, link.src().port());
383 externalPorts.put(dst, link.dst().port());
384 internalPorts.remove(src, link.src().port());
385 internalPorts.remove(dst, link.dst().port());
386 }
387
388 break;
389 case LINK_UPDATED:
390 // don't care about links being updated.
391 break;
392 default:
393 break;
394 }
395
396 }
397
398 }
399
400 public class InternalDeviceListener implements DeviceListener {
401
402 @Override
403 public void event(DeviceEvent event) {
404 Device device = event.subject();
405 switch (event.type()) {
406 case DEVICE_ADDED:
407 case DEVICE_AVAILABILITY_CHANGED:
408 case DEVICE_MASTERSHIP_CHANGED:
409 case DEVICE_SUSPENDED:
410 case DEVICE_UPDATED:
alshabibb5522ff2014-09-29 19:20:00 -0700411 // nothing to do in these cases; handled when links get reported
412 break;
413 case DEVICE_REMOVED:
414 synchronized (externalPorts) {
415 externalPorts.removeAll(device);
416 internalPorts.removeAll(device);
417 }
418 break;
419 case PORT_ADDED:
alshabib6eb438a2014-10-01 16:39:37 -0700420 case PORT_UPDATED:
alshabibb5522ff2014-09-29 19:20:00 -0700421 synchronized (externalPorts) {
alshabib6eb438a2014-10-01 16:39:37 -0700422 if (event.port().isEnabled()) {
423 externalPorts.put(device, event.port().number());
424 internalPorts.remove(device, event.port().number());
425 }
alshabibb5522ff2014-09-29 19:20:00 -0700426 }
427 break;
428 case PORT_REMOVED:
429 synchronized (externalPorts) {
430 externalPorts.remove(device, event.port().number());
431 internalPorts.remove(device, event.port().number());
432 }
433 break;
434 default:
435 break;
436
437 }
438
439 }
440
alshabibc274c902014-10-03 14:58:27 -0700441 }
alshabibb5522ff2014-09-29 19:20:00 -0700442
443}