blob: 6b84e45e11956ae7804394317a02f089bc98b9d9 [file] [log] [blame]
Jonathan Hart0961fe82014-04-03 09:56:25 -07001package net.onrc.onos.apps.proxyarp;
Jonathan Hartc7ca35d2013-06-25 20:54:25 +12002
Jonathan Hartc7ca35d2013-06-25 20:54:25 +12003import java.net.InetAddress;
4import java.net.UnknownHostException;
TeruU7feef8a2014-04-03 00:15:49 -07005import java.nio.ByteBuffer;
Jonathan Hartc7ca35d2013-06-25 20:54:25 +12006import java.util.ArrayList;
7import java.util.Collection;
Jonathan Harte93aed42013-12-05 18:39:50 -08008import java.util.HashMap;
Jonathan Hart6261dcd2013-07-22 17:58:35 +12009import java.util.Iterator;
Jonathan Hartc7ca35d2013-06-25 20:54:25 +120010import java.util.List;
11import java.util.Map;
12import java.util.Set;
Jonathan Hart6261dcd2013-07-22 17:58:35 +120013import java.util.Timer;
14import java.util.TimerTask;
Jonathan Hartc7ca35d2013-06-25 20:54:25 +120015
16import net.floodlightcontroller.core.FloodlightContext;
17import net.floodlightcontroller.core.IFloodlightProviderService;
18import net.floodlightcontroller.core.IOFMessageListener;
19import net.floodlightcontroller.core.IOFSwitch;
Jonathan Harte93aed42013-12-05 18:39:50 -080020import net.floodlightcontroller.core.module.FloodlightModuleContext;
21import net.floodlightcontroller.core.module.IFloodlightModule;
22import net.floodlightcontroller.core.module.IFloodlightService;
Jonathan Hart5afde492013-10-01 12:30:53 +130023import net.floodlightcontroller.restserver.IRestApiService;
Jonathan Hart8ec133c2013-06-26 15:25:18 +120024import net.floodlightcontroller.util.MACAddress;
Jonathan Hart8f6dc092014-04-18 15:56:43 -070025import net.onrc.onos.apps.sdnip.Interface;
Jonathan Hart6df90172014-04-03 10:13:11 -070026import net.onrc.onos.core.datagrid.IDatagridService;
27import net.onrc.onos.core.datagrid.IEventChannel;
28import net.onrc.onos.core.datagrid.IEventChannelListener;
Jonathan Hart23701d12014-04-03 10:45:48 -070029import net.onrc.onos.core.devicemanager.IOnosDeviceService;
30import net.onrc.onos.core.flowprogrammer.IFlowPusherService;
Jonathan Hart51f6f5b2014-04-03 10:32:10 -070031import net.onrc.onos.core.main.config.IConfigInfoService;
Jonathan Hartdeda0ba2014-04-03 11:14:12 -070032import net.onrc.onos.core.packet.ARP;
33import net.onrc.onos.core.packet.Ethernet;
34import net.onrc.onos.core.packet.IPv4;
Jonathan Hart313fdf02014-04-10 14:09:46 -070035import net.onrc.onos.core.packetservice.BroadcastPacketOutNotification;
36import net.onrc.onos.core.packetservice.SinglePacketOutNotification;
Jonathan Hart472062d2014-04-03 10:56:48 -070037import net.onrc.onos.core.topology.Device;
38import net.onrc.onos.core.topology.INetworkGraphService;
39import net.onrc.onos.core.topology.NetworkGraph;
40import net.onrc.onos.core.topology.Switch;
Jonathan Hart23701d12014-04-03 10:45:48 -070041import net.onrc.onos.core.util.Dpid;
Jonathan Hart23701d12014-04-03 10:45:48 -070042import net.onrc.onos.core.util.SwitchPort;
Jonathan Hartc7ca35d2013-06-25 20:54:25 +120043
44import org.openflow.protocol.OFMessage;
45import org.openflow.protocol.OFPacketIn;
46import org.openflow.protocol.OFPacketOut;
47import org.openflow.protocol.OFPort;
48import org.openflow.protocol.OFType;
49import org.openflow.protocol.action.OFAction;
50import org.openflow.protocol.action.OFActionOutput;
Jonathan Hart8ec133c2013-06-26 15:25:18 +120051import org.openflow.util.HexString;
Jonathan Hartc7ca35d2013-06-25 20:54:25 +120052import org.slf4j.Logger;
53import org.slf4j.LoggerFactory;
54
Jonathan Hart4dfc3652013-08-02 20:22:36 +120055import com.google.common.collect.HashMultimap;
56import com.google.common.collect.Multimaps;
57import com.google.common.collect.SetMultimap;
58
Jonathan Hart18ad55c2013-11-11 22:49:55 -080059public class ProxyArpManager implements IProxyArpService, IOFMessageListener,
Ray Milkey269ffb92014-04-03 14:43:30 -070060 IFloodlightModule {
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -070061 private static final Logger log = LoggerFactory
62 .getLogger(ProxyArpManager.class);
pingping-lin017a8922013-12-11 11:15:33 +080063
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -070064 private static final long ARP_TIMER_PERIOD = 100; // ms
Jonathan Hartdf6ec332013-08-04 01:37:14 +120065
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -070066 private static final int ARP_REQUEST_TIMEOUT = 2000; // ms
Jonathan Hartda4d0e12013-09-30 21:00:20 +130067
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -070068 private IFloodlightProviderService floodlightProvider;
69 private IDatagridService datagrid;
TeruU7feef8a2014-04-03 00:15:49 -070070 private IEventChannel<Long, ArpReplyNotification> arpReplyEventChannel;
71 private IEventChannel<Long, BroadcastPacketOutNotification> broadcastPacketOutEventChannel;
72 private IEventChannel<Long, SinglePacketOutNotification> singlePacketOutEventChannel;
Pavlin Radoslavov902fe522014-03-31 10:11:31 -070073 private static final String ARP_REPLY_CHANNEL_NAME = "onos.arp_reply";
TeruU7feef8a2014-04-03 00:15:49 -070074 private static final String BROADCAST_PACKET_OUT_CHANNEL_NAME = "onos.broadcast_packet_out";
75 private static final String SINGLE_PACKET_OUT_CHANNEL_NAME = "onos.single_packet_out";
76 private ArpReplyEventHandler arpReplyEventHandler = new ArpReplyEventHandler();
Ray Milkey269ffb92014-04-03 14:43:30 -070077 private BroadcastPacketOutEventHandler broadcastPacketOutEventHandler = new BroadcastPacketOutEventHandler();
TeruU7feef8a2014-04-03 00:15:49 -070078 private SinglePacketOutEventHandler singlePacketOutEventHandler = new SinglePacketOutEventHandler();
Pavlin Radoslavov902fe522014-03-31 10:11:31 -070079
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -070080 private IConfigInfoService configService;
81 private IRestApiService restApi;
82 private IFlowPusherService flowPusher;
Ray Milkey269ffb92014-04-03 14:43:30 -070083
84 private INetworkGraphService networkGraphService;
85 private NetworkGraph networkGraph;
86 private IOnosDeviceService onosDeviceService;
Jonathan Harte93aed42013-12-05 18:39:50 -080087
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -070088 private short vlan;
89 private static final short NO_VLAN = 0;
Jonathan Harte93aed42013-12-05 18:39:50 -080090
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -070091 private SetMultimap<InetAddress, ArpRequest> arpRequests;
Jonathan Hartdf6ec332013-08-04 01:37:14 +120092
TeruU7feef8a2014-04-03 00:15:49 -070093 private class BroadcastPacketOutEventHandler implements
Ray Milkey269ffb92014-04-03 14:43:30 -070094 IEventChannelListener<Long, BroadcastPacketOutNotification> {
Pavlin Radoslavov902fe522014-03-31 10:11:31 -070095
Ray Milkey269ffb92014-04-03 14:43:30 -070096 @Override
97 public void entryAdded(BroadcastPacketOutNotification value) {
98 if (log.isTraceEnabled()) {
Jonathan Hart313fdf02014-04-10 14:09:46 -070099 log.trace("entryAdded ip{}, sw {}, port {}, packet {}", value.getTargetAddress(), value.getInSwitch(), value.getInPort(), value.getPacketData().length);
Ray Milkey269ffb92014-04-03 14:43:30 -0700100 }
101 BroadcastPacketOutNotification notification = (BroadcastPacketOutNotification) value;
Jonathan Hart313fdf02014-04-10 14:09:46 -0700102 broadcastArpRequestOutMyEdge(notification.getPacketData(),
Ray Milkey269ffb92014-04-03 14:43:30 -0700103 notification.getInSwitch(),
104 notification.getInPort());
105
106 // set timestamp
107 ByteBuffer buffer = ByteBuffer.allocate(4);
108 buffer.putInt(notification.getTargetAddress());
109 InetAddress addr = null;
110 try {
111 addr = InetAddress.getByAddress(buffer.array());
112 } catch (UnknownHostException e) {
113 log.error("Exception:", e);
114 }
115
116 if (addr != null) {
117 for (ArpRequest request : arpRequests.get(addr)) {
118 request.setRequestTime();
119 }
120 }
121 }
122
123 @Override
124 public void entryUpdated(BroadcastPacketOutNotification value) {
125 log.debug("entryUpdated");
126 // TODO: For now, entryUpdated() is processed as entryAdded()
127 entryAdded(value);
128 }
129
130 @Override
131 public void entryRemoved(BroadcastPacketOutNotification value) {
132 log.debug("entryRemoved");
133 // TODO: Not implemented. Revisit when this module is refactored
134 }
TeruU7feef8a2014-04-03 00:15:49 -0700135 }
Ray Milkey269ffb92014-04-03 14:43:30 -0700136
TeruU7feef8a2014-04-03 00:15:49 -0700137 private class SinglePacketOutEventHandler implements
Ray Milkey269ffb92014-04-03 14:43:30 -0700138 IEventChannelListener<Long, SinglePacketOutNotification> {
139 @Override
140 public void entryAdded(SinglePacketOutNotification packetOutNotification) {
141 log.debug("entryAdded");
142 SinglePacketOutNotification notification =
143 (SinglePacketOutNotification) packetOutNotification;
Jonathan Hart313fdf02014-04-10 14:09:46 -0700144 sendArpRequestOutPort(notification.getPacketData(),
Ray Milkey269ffb92014-04-03 14:43:30 -0700145 notification.getOutSwitch(),
146 notification.getOutPort());
147
148 // set timestamp
149 ByteBuffer buffer = ByteBuffer.allocate(4);
150 buffer.putInt(notification.getTargetAddress());
151 InetAddress addr = null;
152 try {
153 addr = InetAddress.getByAddress(buffer.array());
154 } catch (UnknownHostException e) {
155 log.error("Exception:", e);
156 }
157
158 if (addr != null) {
159 for (ArpRequest request : arpRequests.get(addr)) {
160 request.setRequestTime();
161 }
162 }
163 }
164
165 @Override
166 public void entryUpdated(SinglePacketOutNotification packetOutNotification) {
167 log.debug("entryUpdated");
168 // TODO: For now, entryUpdated() is processed as entryAdded()
169 entryAdded(packetOutNotification);
170 }
171
172 @Override
173 public void entryRemoved(SinglePacketOutNotification packetOutNotification) {
174 log.debug("entryRemoved");
175 // TODO: Not implemented. Revisit when this module is refactored
176 }
Pavlin Radoslavov902fe522014-03-31 10:11:31 -0700177 }
178
Pavlin Radoslavov902fe522014-03-31 10:11:31 -0700179 private class ArpReplyEventHandler implements
Ray Milkey269ffb92014-04-03 14:43:30 -0700180 IEventChannelListener<Long, ArpReplyNotification> {
Pavlin Radoslavov902fe522014-03-31 10:11:31 -0700181
Ray Milkey269ffb92014-04-03 14:43:30 -0700182 @Override
183 public void entryAdded(ArpReplyNotification arpReply) {
184 log.debug("Received ARP reply notification for ip {}, mac {}",
185 arpReply.getTargetAddress(), arpReply.getTargetMacAddress());
186 ByteBuffer buffer = ByteBuffer.allocate(4);
187 buffer.putInt(arpReply.getTargetAddress());
188 InetAddress addr = null;
189 try {
190 addr = InetAddress.getByAddress(buffer.array());
191 } catch (UnknownHostException e) {
192 log.error("Exception:", e);
193 }
Pavlin Radoslavov902fe522014-03-31 10:11:31 -0700194
Ray Milkey269ffb92014-04-03 14:43:30 -0700195 if (addr != null) {
196 sendArpReplyToWaitingRequesters(addr,
197 arpReply.getTargetMacAddress());
198 }
199 }
200
201 @Override
202 public void entryUpdated(ArpReplyNotification arpReply) {
203 // TODO: For now, entryUpdated() is processed as entryAdded()
204 entryAdded(arpReply);
205 }
206
207 @Override
208 public void entryRemoved(ArpReplyNotification arpReply) {
209 // TODO: Not implemented. Revisit when this module is refactored
210 }
Pavlin Radoslavov902fe522014-03-31 10:11:31 -0700211 }
212
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700213 private static class ArpRequest {
214 private final IArpRequester requester;
215 private final boolean retry;
216 private boolean sent = false;
217 private long requestTime;
Jonathan Hartdf6ec332013-08-04 01:37:14 +1200218
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700219 public ArpRequest(IArpRequester requester, boolean retry) {
220 this.requester = requester;
221 this.retry = retry;
222 }
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200223
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700224 public ArpRequest(ArpRequest old) {
225 this.requester = old.requester;
226 this.retry = old.retry;
227 }
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200228
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700229 public boolean isExpired() {
230 return sent
231 && ((System.currentTimeMillis() - requestTime) > ARP_REQUEST_TIMEOUT);
232 }
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200233
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700234 public boolean shouldRetry() {
235 return retry;
236 }
237
238 public void dispatchReply(InetAddress ipAddress,
Ray Milkey269ffb92014-04-03 14:43:30 -0700239 MACAddress replyMacAddress) {
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700240 requester.arpResponse(ipAddress, replyMacAddress);
241 }
242
243 public void setRequestTime() {
244 this.requestTime = System.currentTimeMillis();
245 this.sent = true;
246 }
247 }
248
249 private class HostArpRequester implements IArpRequester {
250 private final ARP arpRequest;
251 private final long dpid;
252 private final short port;
253
254 public HostArpRequester(ARP arpRequest, long dpid, short port) {
255 this.arpRequest = arpRequest;
256 this.dpid = dpid;
257 this.port = port;
258 }
259
260 @Override
261 public void arpResponse(InetAddress ipAddress, MACAddress macAddress) {
262 ProxyArpManager.this.sendArpReply(arpRequest, dpid, port,
263 macAddress);
264 }
Ray Milkey269ffb92014-04-03 14:43:30 -0700265
266 public ARP getArpRequest() {
267 return arpRequest;
268 }
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700269 }
270
271 @Override
272 public Collection<Class<? extends IFloodlightService>> getModuleServices() {
273 Collection<Class<? extends IFloodlightService>> l =
274 new ArrayList<Class<? extends IFloodlightService>>();
275 l.add(IProxyArpService.class);
276 return l;
277 }
278
279 @Override
280 public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
281 Map<Class<? extends IFloodlightService>, IFloodlightService> m =
282 new HashMap<Class<? extends IFloodlightService>, IFloodlightService>();
283 m.put(IProxyArpService.class, this);
284 return m;
285 }
286
287 @Override
288 public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
289 Collection<Class<? extends IFloodlightService>> dependencies =
290 new ArrayList<Class<? extends IFloodlightService>>();
291 dependencies.add(IFloodlightProviderService.class);
292 dependencies.add(IRestApiService.class);
293 dependencies.add(IDatagridService.class);
294 dependencies.add(IConfigInfoService.class);
295 dependencies.add(IFlowPusherService.class);
TeruU7feef8a2014-04-03 00:15:49 -0700296 dependencies.add(INetworkGraphService.class);
297 dependencies.add(IOnosDeviceService.class);
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700298 return dependencies;
299 }
300
301 @Override
302 public void init(FloodlightModuleContext context) {
Ray Milkey269ffb92014-04-03 14:43:30 -0700303 this.floodlightProvider = context.getServiceImpl(IFloodlightProviderService.class);
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700304 this.configService = context.getServiceImpl(IConfigInfoService.class);
305 this.restApi = context.getServiceImpl(IRestApiService.class);
TeruU7feef8a2014-04-03 00:15:49 -0700306 this.datagrid = context.getServiceImpl(IDatagridService.class);
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700307 this.flowPusher = context.getServiceImpl(IFlowPusherService.class);
TeruU7feef8a2014-04-03 00:15:49 -0700308 this.networkGraphService = context.getServiceImpl(INetworkGraphService.class);
309 this.onosDeviceService = context.getServiceImpl(IOnosDeviceService.class);
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700310
311 // arpCache = new ArpCache();
312
313 arpRequests = Multimaps.synchronizedSetMultimap(HashMultimap
314 .<InetAddress, ArpRequest>create());
315
316 }
317
318 @Override
319 public void startUp(FloodlightModuleContext context) {
320 this.vlan = configService.getVlan();
321 log.info("vlan set to {}", this.vlan);
322
323 restApi.addRestletRoutable(new ArpWebRoutable());
324 floodlightProvider.addOFMessageListener(OFType.PACKET_IN, this);
Ray Milkey269ffb92014-04-03 14:43:30 -0700325 networkGraph = networkGraphService.getNetworkGraph();
326
327 //
328 // Event notification setup: channels and event handlers
329 //
330 broadcastPacketOutEventChannel = datagrid.addListener(BROADCAST_PACKET_OUT_CHANNEL_NAME,
331 broadcastPacketOutEventHandler,
332 Long.class,
333 BroadcastPacketOutNotification.class);
334
335 singlePacketOutEventChannel = datagrid.addListener(SINGLE_PACKET_OUT_CHANNEL_NAME,
336 singlePacketOutEventHandler,
337 Long.class,
338 SinglePacketOutNotification.class);
339
340 arpReplyEventChannel = datagrid.addListener(ARP_REPLY_CHANNEL_NAME,
341 arpReplyEventHandler,
342 Long.class,
343 ArpReplyNotification.class);
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700344
345 Timer arpTimer = new Timer("arp-processing");
346 arpTimer.scheduleAtFixedRate(new TimerTask() {
347 @Override
348 public void run() {
349 doPeriodicArpProcessing();
350 }
351 }, 0, ARP_TIMER_PERIOD);
352 }
353
354 /*
355 * Function that runs periodically to manage the asynchronous request mechanism.
356 * It basically cleans up old ARP requests if we don't get a response for them.
357 * The caller can designate that a request should be retried indefinitely, and
358 * this task will handle that as well.
359 */
360 private void doPeriodicArpProcessing() {
361 SetMultimap<InetAddress, ArpRequest> retryList = HashMultimap
362 .<InetAddress, ArpRequest>create();
363
364 // Have to synchronize externally on the Multimap while using an
365 // iterator,
366 // even though it's a synchronizedMultimap
367 synchronized (arpRequests) {
368 Iterator<Map.Entry<InetAddress, ArpRequest>> it = arpRequests
369 .entries().iterator();
370
371 while (it.hasNext()) {
372 Map.Entry<InetAddress, ArpRequest> entry = it.next();
373 ArpRequest request = entry.getValue();
374 if (request.isExpired()) {
375 log.debug("Cleaning expired ARP request for {}", entry
376 .getKey().getHostAddress());
377
Ray Milkey269ffb92014-04-03 14:43:30 -0700378 // If the ARP request is expired and then delete the device
379 // TODO check whether this is OK from this thread
380 HostArpRequester requester = (HostArpRequester) request.requester;
381 ARP req = requester.getArpRequest();
382 Device targetDev = networkGraph.getDeviceByMac(MACAddress.valueOf(req.getTargetHardwareAddress()));
383 if (targetDev != null) {
384 onosDeviceService.deleteOnosDeviceByMac(MACAddress.valueOf(req.getTargetHardwareAddress()));
385 if (log.isDebugEnabled()) {
386 log.debug("RemoveDevice: {} due to no have not recieve the ARP reply", targetDev.getMacAddress());
387 }
388 }
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700389
390 it.remove();
391
392 if (request.shouldRetry()) {
393 retryList.put(entry.getKey(), request);
394 }
395 }
396 }
397 }
398
399 for (Map.Entry<InetAddress, Collection<ArpRequest>> entry : retryList
400 .asMap().entrySet()) {
401
402 InetAddress address = entry.getKey();
403
404 log.debug("Resending ARP request for {}", address.getHostAddress());
405
406 // Only ARP requests sent by the controller will have the retry flag
407 // set, so for now we can just send a new ARP request for that
408 // address.
409 sendArpRequestForAddress(address);
410
411 for (ArpRequest request : entry.getValue()) {
412 arpRequests.put(address, new ArpRequest(request));
413 }
414 }
415 }
416
417 @Override
418 public String getName() {
419 return "proxyarpmanager";
420 }
421
422 @Override
423 public boolean isCallbackOrderingPrereq(OFType type, String name) {
424 if (type == OFType.PACKET_IN) {
425 return "devicemanager".equals(name)
426 || "onosdevicemanager".equals(name);
427 } else {
428 return false;
429 }
430 }
431
432 @Override
433 public boolean isCallbackOrderingPostreq(OFType type, String name) {
434 return type == OFType.PACKET_IN && "onosforwarding".equals(name);
435 }
436
437 @Override
438 public Command receive(IOFSwitch sw, OFMessage msg, FloodlightContext cntx) {
Jonathan Hart8ed69c52014-04-09 13:29:16 -0700439 if (!(msg instanceof OFPacketIn)) {
440 return Command.CONTINUE;
441 }
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700442
443 OFPacketIn pi = (OFPacketIn) msg;
444
445 Ethernet eth = IFloodlightProviderService.bcStore.get(cntx,
Jonathan Hartc7ca35d2013-06-25 20:54:25 +1200446 IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
Jonathan Hart2f790d22013-08-15 14:01:24 +1200447
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700448 if (eth.getEtherType() == Ethernet.TYPE_ARP) {
449 ARP arp = (ARP) eth.getPayload();
450 if (arp.getOpCode() == ARP.OP_REQUEST) {
451 handleArpRequest(sw, pi, arp, eth);
452 } else if (arp.getOpCode() == ARP.OP_REPLY) {
453 // For replies we simply send a notification via Hazelcast
454 sendArpReplyNotification(eth, pi);
Jonathan Hart5b803bc2013-09-23 14:46:11 +1200455
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700456 // handleArpReply(sw, pi, arp);
457 }
pingping-linb8757bf2013-12-13 01:48:58 +0800458
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700459 // Stop ARP packets here
460 return Command.STOP;
461 }
pingping-linb8757bf2013-12-13 01:48:58 +0800462
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700463 // Propagate everything else
464 return Command.CONTINUE;
465 }
pingping-linb8757bf2013-12-13 01:48:58 +0800466
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700467 private void handleArpRequest(IOFSwitch sw, OFPacketIn pi, ARP arp,
Ray Milkey269ffb92014-04-03 14:43:30 -0700468 Ethernet eth) {
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700469 if (log.isTraceEnabled()) {
470 log.trace("ARP request received for {}",
471 inetAddressToString(arp.getTargetProtocolAddress()));
472 }
pingping-linb8757bf2013-12-13 01:48:58 +0800473
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700474 InetAddress target;
475 try {
476 target = InetAddress.getByAddress(arp.getTargetProtocolAddress());
477 } catch (UnknownHostException e) {
478 log.debug("Invalid address in ARP request", e);
479 return;
480 }
Pavlin Radoslavov4cf8ee52014-03-26 18:19:58 -0700481
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700482 if (configService.fromExternalNetwork(sw.getId(), pi.getInPort())) {
483 // If the request came from outside our network, we only care if
484 // it was a request for one of our interfaces.
485 if (configService.isInterfaceAddress(target)) {
486 log.trace(
487 "ARP request for our interface. Sending reply {} => {}",
488 target.getHostAddress(),
489 configService.getRouterMacAddress());
pingping-linb8757bf2013-12-13 01:48:58 +0800490
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700491 sendArpReply(arp, sw.getId(), pi.getInPort(),
492 configService.getRouterMacAddress());
493 }
pingping-linb8757bf2013-12-13 01:48:58 +0800494
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700495 return;
496 }
pingping-linb8757bf2013-12-13 01:48:58 +0800497
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700498 // MACAddress macAddress = arpCache.lookup(target);
pingping-linb8757bf2013-12-13 01:48:58 +0800499
Ray Milkey269ffb92014-04-03 14:43:30 -0700500 arpRequests.put(target, new ArpRequest(
501 new HostArpRequester(arp, sw.getId(), pi.getInPort()), false));
pingping-linb8757bf2013-12-13 01:48:58 +0800502
Ray Milkey269ffb92014-04-03 14:43:30 -0700503 Device targetDevice = networkGraph.getDeviceByMac(MACAddress.valueOf(arp.getTargetHardwareAddress()));
pingping-linb8757bf2013-12-13 01:48:58 +0800504
Ray Milkey269ffb92014-04-03 14:43:30 -0700505 if (targetDevice == null) {
506 if (log.isTraceEnabled()) {
507 log.trace("No device info found for {} - broadcasting",
508 target.getHostAddress());
509 }
Jonathan Hart18ad9502013-12-15 18:28:00 -0800510
Ray Milkey269ffb92014-04-03 14:43:30 -0700511 // We don't know the device so broadcast the request out
512 BroadcastPacketOutNotification key =
513 new BroadcastPacketOutNotification(eth.serialize(),
514 ByteBuffer.wrap(arp.getTargetProtocolAddress()).getInt(), sw.getId(), pi.getInPort());
515 log.debug("broadcastPacketOutEventChannel mac {}, ip {}, dpid {}, port {}, paket {}", eth.getSourceMAC().toLong(),
516 ByteBuffer.wrap(arp.getTargetProtocolAddress()).getInt(), sw.getId(), pi.getInPort(), eth.serialize().length);
517 broadcastPacketOutEventChannel.addTransientEntry(eth.getDestinationMAC().toLong(), key);
518 } else {
519 // Even if the device exists in our database, we do not reply to
520 // the request directly, but check whether the device is still valid
521 MACAddress macAddress = MACAddress.valueOf(arp.getTargetHardwareAddress());
Jonathan Hart2f790d22013-08-15 14:01:24 +1200522
Ray Milkey269ffb92014-04-03 14:43:30 -0700523 if (log.isTraceEnabled()) {
524 log.trace("The target Device Record in DB is: {} => {} from ARP request host at {}/{}",
525 new Object[]{
526 inetAddressToString(arp.getTargetProtocolAddress()),
527 macAddress,
528 HexString.toHexString(sw.getId()), pi.getInPort()});
529 }
Jonathan Hart5b803bc2013-09-23 14:46:11 +1200530
Ray Milkey269ffb92014-04-03 14:43:30 -0700531 // sendArpReply(arp, sw.getId(), pi.getInPort(), macAddress);
Jonathan Hart7804bea2014-01-07 10:50:52 -0800532
Ray Milkey269ffb92014-04-03 14:43:30 -0700533 Iterable<net.onrc.onos.core.topology.Port> outPorts = targetDevice.getAttachmentPoints();
534
535 if (!outPorts.iterator().hasNext()) {
536 if (log.isTraceEnabled()) {
537 log.trace("Device {} exists but is not connected to any ports" +
538 " - broadcasting", macAddress);
539 }
540
541// BroadcastPacketOutNotification key =
Ray Milkey5d406012014-04-08 14:44:41 -0700542// new BroadcastPacketOutNotification(eth.serialize(),
Ray Milkey269ffb92014-04-03 14:43:30 -0700543// target, sw.getId(), pi.getInPort());
544// broadcastPacketOutEventChannel.addTransientEntry(eth.getDestinationMAC().toLong(), key);
545 } else {
546 for (net.onrc.onos.core.topology.Port portObject : outPorts) {
547 //long outSwitch = 0;
548 //short outPort = 0;
549
550 if (portObject.getOutgoingLink() != null || portObject.getIncomingLink() != null) {
551 continue;
552 }
553
554 short outPort = portObject.getNumber().shortValue();
555 Switch outSwitchObject = portObject.getSwitch();
556 long outSwitch = outSwitchObject.getDpid();
557
558 if (log.isTraceEnabled()) {
559 log.trace("Probing device {} on port {}/{}",
560 new Object[]{macAddress,
561 HexString.toHexString(outSwitch), outPort});
562 }
563
564 SinglePacketOutNotification key =
565 new SinglePacketOutNotification(eth.serialize(),
566 ByteBuffer.wrap(target.getAddress()).getInt(), outSwitch, outPort);
567 singlePacketOutEventChannel.addTransientEntry(eth.getDestinationMAC().toLong(), key);
568 }
569 }
570 }
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700571 }
pingping-linb8757bf2013-12-13 01:48:58 +0800572
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700573 // Not used because device manager currently updates the database
574 // for ARP replies. May be useful in the future.
575 private void handleArpReply(IOFSwitch sw, OFPacketIn pi, ARP arp) {
576 if (log.isTraceEnabled()) {
Ray Milkey269ffb92014-04-03 14:43:30 -0700577 log.trace("ARP reply recieved: {} => {}, on {}/{}", new Object[]{
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700578 inetAddressToString(arp.getSenderProtocolAddress()),
579 HexString.toHexString(arp.getSenderHardwareAddress()),
580 HexString.toHexString(sw.getId()), pi.getInPort()});
581 }
Jonathan Hart7804bea2014-01-07 10:50:52 -0800582
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700583 InetAddress senderIpAddress;
584 try {
585 senderIpAddress = InetAddress.getByAddress(arp
586 .getSenderProtocolAddress());
587 } catch (UnknownHostException e) {
588 log.debug("Invalid address in ARP reply", e);
589 return;
590 }
Jonathan Hart7804bea2014-01-07 10:50:52 -0800591
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700592 MACAddress senderMacAddress = MACAddress.valueOf(arp
593 .getSenderHardwareAddress());
594
595 // See if anyone's waiting for this ARP reply
596 Set<ArpRequest> requests = arpRequests.get(senderIpAddress);
597
598 // Synchronize on the Multimap while using an iterator for one of the
599 // sets
600 List<ArpRequest> requestsToSend = new ArrayList<ArpRequest>(
601 requests.size());
602 synchronized (arpRequests) {
603 Iterator<ArpRequest> it = requests.iterator();
604 while (it.hasNext()) {
605 ArpRequest request = it.next();
606 it.remove();
607 requestsToSend.add(request);
608 }
609 }
610
611 // Don't hold an ARP lock while dispatching requests
612 for (ArpRequest request : requestsToSend) {
613 request.dispatchReply(senderIpAddress, senderMacAddress);
614 }
615 }
616
617 private void sendArpRequestForAddress(InetAddress ipAddress) {
618 // TODO what should the sender IP address and MAC address be if no
619 // IP addresses are configured? Will there ever be a need to send
620 // ARP requests from the controller in that case?
621 // All-zero MAC address doesn't seem to work - hosts don't respond to it
622
623 byte[] zeroIpv4 = {0x0, 0x0, 0x0, 0x0};
624 byte[] zeroMac = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0};
625 byte[] genericNonZeroMac = {0x0, 0x0, 0x0, 0x0, 0x0, 0x01};
626 byte[] broadcastMac = {(byte) 0xff, (byte) 0xff, (byte) 0xff,
627 (byte) 0xff, (byte) 0xff, (byte) 0xff};
628
629 ARP arpRequest = new ARP();
630
631 arpRequest
632 .setHardwareType(ARP.HW_TYPE_ETHERNET)
633 .setProtocolType(ARP.PROTO_TYPE_IP)
634 .setHardwareAddressLength(
635 (byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
636 .setProtocolAddressLength((byte) IPv4.ADDRESS_LENGTH)
637 .setOpCode(ARP.OP_REQUEST).setTargetHardwareAddress(zeroMac)
638 .setTargetProtocolAddress(ipAddress.getAddress());
639
640 MACAddress routerMacAddress = configService.getRouterMacAddress();
641 // TODO hack for now as it's unclear what the MAC address should be
642 byte[] senderMacAddress = genericNonZeroMac;
643 if (routerMacAddress != null) {
644 senderMacAddress = routerMacAddress.toBytes();
645 }
646 arpRequest.setSenderHardwareAddress(senderMacAddress);
647
648 byte[] senderIPAddress = zeroIpv4;
649 Interface intf = configService.getOutgoingInterface(ipAddress);
Jonathan Hart8ed69c52014-04-09 13:29:16 -0700650 if (intf == null) {
651 // TODO handle the case where the controller needs to send an ARP
652 // request but there's not IP configuration. In this case the
653 // request should be broadcast out all edge ports in the network.
654 log.warn("Sending ARP requests with default configuration "
655 + "not supported");
656 return;
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700657 }
658
Jonathan Hart8ed69c52014-04-09 13:29:16 -0700659 senderIPAddress = intf.getIpAddress().getAddress();
660
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700661 arpRequest.setSenderProtocolAddress(senderIPAddress);
662
663 Ethernet eth = new Ethernet();
664 eth.setSourceMACAddress(senderMacAddress)
665 .setDestinationMACAddress(broadcastMac)
666 .setEtherType(Ethernet.TYPE_ARP).setPayload(arpRequest);
667
668 if (vlan != NO_VLAN) {
669 eth.setVlanID(vlan).setPriorityCode((byte) 0);
670 }
671
672 // sendArpRequestToSwitches(ipAddress, eth.serialize());
Ray Milkey269ffb92014-04-03 14:43:30 -0700673 SinglePacketOutNotification key =
674 new SinglePacketOutNotification(eth.serialize(), ByteBuffer.wrap(ipAddress.getAddress()).getInt(),
675 intf.getDpid(), intf.getPort());
676 singlePacketOutEventChannel.addTransientEntry(MACAddress.valueOf(senderMacAddress).toLong(), key);
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700677 }
Ray Milkey269ffb92014-04-03 14:43:30 -0700678
Pavlin Radoslavovf1fdc7a2014-04-10 16:05:28 -0700679 /*
TeruU7feef8a2014-04-03 00:15:49 -0700680 private void sendArpRequestToSwitches(InetAddress dstAddress, byte[] arpRequest) {
Ray Milkey269ffb92014-04-03 14:43:30 -0700681 sendArpRequestToSwitches(dstAddress, arpRequest, 0,
682 OFPort.OFPP_NONE.getValue());
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700683 }
Pavlin Radoslavovf1fdc7a2014-04-10 16:05:28 -0700684 */
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700685
Pavlin Radoslavovf1fdc7a2014-04-10 16:05:28 -0700686 /*
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700687 private void sendArpRequestToSwitches(InetAddress dstAddress,
Ray Milkey269ffb92014-04-03 14:43:30 -0700688 byte[] arpRequest, long inSwitch, short inPort) {
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700689
690 if (configService.hasLayer3Configuration()) {
691 Interface intf = configService.getOutgoingInterface(dstAddress);
692 if (intf == null) {
693 // TODO here it should be broadcast out all non-interface edge
694 // ports.
695 // I think we can assume that if it's not a request for an
696 // external
697 // network, it's an ARP for a host in our own network. So we
698 // want to
699 // send it out all edge ports that don't have an interface
700 // configured
701 // to ensure it reaches all hosts in our network.
702 log.debug("No interface found to send ARP request for {}",
703 dstAddress.getHostAddress());
704 } else {
705 sendArpRequestOutPort(arpRequest, intf.getDpid(),
706 intf.getPort());
707 }
708 } else {
709 // broadcastArpRequestOutEdge(arpRequest, inSwitch, inPort);
710 broadcastArpRequestOutMyEdge(arpRequest, inSwitch, inPort);
711 }
712 }
Pavlin Radoslavovf1fdc7a2014-04-10 16:05:28 -0700713 */
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700714
715 private void sendArpReplyNotification(Ethernet eth, OFPacketIn pi) {
716 ARP arp = (ARP) eth.getPayload();
717
718 if (log.isTraceEnabled()) {
719 log.trace("Sending ARP reply for {} to other ONOS instances",
720 inetAddressToString(arp.getSenderProtocolAddress()));
721 }
722
723 InetAddress targetAddress;
724
725 try {
726 targetAddress = InetAddress.getByAddress(arp
727 .getSenderProtocolAddress());
728 } catch (UnknownHostException e) {
729 log.error("Unknown host", e);
730 return;
731 }
732
733 MACAddress mac = new MACAddress(arp.getSenderHardwareAddress());
734
Ray Milkey269ffb92014-04-03 14:43:30 -0700735 ArpReplyNotification key =
736 new ArpReplyNotification(ByteBuffer.wrap(targetAddress.getAddress()).getInt(), mac);
737 log.debug("ArpReplyNotification ip {}, mac{}", ByteBuffer.wrap(targetAddress.getAddress()).getInt(), mac);
738 arpReplyEventChannel.addTransientEntry(mac.toLong(), key);
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700739 }
740
741 private void broadcastArpRequestOutMyEdge(byte[] arpRequest, long inSwitch,
Ray Milkey269ffb92014-04-03 14:43:30 -0700742 short inPort) {
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700743 List<SwitchPort> switchPorts = new ArrayList<SwitchPort>();
744
745 for (IOFSwitch sw : floodlightProvider.getSwitches().values()) {
746
747 OFPacketOut po = new OFPacketOut();
748 po.setInPort(OFPort.OFPP_NONE).setBufferId(-1)
749 .setPacketData(arpRequest);
750
751 List<OFAction> actions = new ArrayList<OFAction>();
752
Ray Milkey269ffb92014-04-03 14:43:30 -0700753 Switch graphSw = networkGraph.getSwitch(sw.getId());
754 Collection<net.onrc.onos.core.topology.Port> ports = graphSw.getPorts();
755
756 if (ports == null) {
757 continue;
758 }
759
760 for (net.onrc.onos.core.topology.Port portObject : ports) {
761 if (portObject.getOutgoingLink() == null && portObject.getNumber() > 0) {
762 Long portNumber = portObject.getNumber();
763
764 if (sw.getId() == inSwitch && portNumber.shortValue() == inPort) {
765 // This is the port that the ARP message came in,
766 // so don't broadcast out this port
767 continue;
768 }
769 switchPorts.add(new SwitchPort(new Dpid(sw.getId()),
770 new net.onrc.onos.core.util.Port(portNumber.shortValue())));
771 actions.add(new OFActionOutput(portNumber.shortValue()));
772 }
773 }
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700774
775 po.setActions(actions);
776 short actionsLength = (short) (actions.size() * OFActionOutput.MINIMUM_LENGTH);
777 po.setActionsLength(actionsLength);
778 po.setLengthU(OFPacketOut.MINIMUM_LENGTH + actionsLength
779 + arpRequest.length);
780
781 flowPusher.add(sw, po);
782 }
783
784 if (log.isTraceEnabled()) {
785 log.trace("Broadcast ARP request to: {}", switchPorts);
786 }
787 }
788
789 private void sendArpRequestOutPort(byte[] arpRequest, long dpid, short port) {
790 if (log.isTraceEnabled()) {
791 log.trace("Sending ARP request out {}/{}",
792 HexString.toHexString(dpid), port);
793 }
794
795 OFPacketOut po = new OFPacketOut();
796 po.setInPort(OFPort.OFPP_NONE).setBufferId(-1)
797 .setPacketData(arpRequest);
798
799 List<OFAction> actions = new ArrayList<OFAction>();
800 actions.add(new OFActionOutput(port));
801 po.setActions(actions);
802 short actionsLength = (short) (actions.size() * OFActionOutput.MINIMUM_LENGTH);
803 po.setActionsLength(actionsLength);
804 po.setLengthU(OFPacketOut.MINIMUM_LENGTH + actionsLength
805 + arpRequest.length);
806
807 IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
808
809 if (sw == null) {
810 log.warn("Switch not found when sending ARP request");
811 return;
812 }
813
814 flowPusher.add(sw, po);
815 }
816
817 private void sendArpReply(ARP arpRequest, long dpid, short port,
Ray Milkey269ffb92014-04-03 14:43:30 -0700818 MACAddress targetMac) {
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700819 if (log.isTraceEnabled()) {
820 log.trace(
821 "Sending reply {} => {} to {}",
Ray Milkey269ffb92014-04-03 14:43:30 -0700822 new Object[]{
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700823 inetAddressToString(arpRequest
824 .getTargetProtocolAddress()),
825 targetMac,
826 inetAddressToString(arpRequest
827 .getSenderProtocolAddress())});
828 }
829
830 ARP arpReply = new ARP();
831 arpReply.setHardwareType(ARP.HW_TYPE_ETHERNET)
832 .setProtocolType(ARP.PROTO_TYPE_IP)
833 .setHardwareAddressLength(
834 (byte) Ethernet.DATALAYER_ADDRESS_LENGTH)
835 .setProtocolAddressLength((byte) IPv4.ADDRESS_LENGTH)
836 .setOpCode(ARP.OP_REPLY)
837 .setSenderHardwareAddress(targetMac.toBytes())
838 .setSenderProtocolAddress(arpRequest.getTargetProtocolAddress())
839 .setTargetHardwareAddress(arpRequest.getSenderHardwareAddress())
840 .setTargetProtocolAddress(arpRequest.getSenderProtocolAddress());
841
842 Ethernet eth = new Ethernet();
843 eth.setDestinationMACAddress(arpRequest.getSenderHardwareAddress())
844 .setSourceMACAddress(targetMac.toBytes())
845 .setEtherType(Ethernet.TYPE_ARP).setPayload(arpReply);
846
847 if (vlan != NO_VLAN) {
848 eth.setVlanID(vlan).setPriorityCode((byte) 0);
849 }
850
851 List<OFAction> actions = new ArrayList<OFAction>();
852 actions.add(new OFActionOutput(port));
853
854 OFPacketOut po = new OFPacketOut();
855 po.setInPort(OFPort.OFPP_NONE)
856 .setBufferId(-1)
857 .setPacketData(eth.serialize())
858 .setActions(actions)
859 .setActionsLength((short) OFActionOutput.MINIMUM_LENGTH)
860 .setLengthU(
861 OFPacketOut.MINIMUM_LENGTH
862 + OFActionOutput.MINIMUM_LENGTH
863 + po.getPacketData().length);
864
865 List<OFMessage> msgList = new ArrayList<OFMessage>();
866 msgList.add(po);
867
868 IOFSwitch sw = floodlightProvider.getSwitches().get(dpid);
869
870 if (sw == null) {
871 log.warn("Switch {} not found when sending ARP reply",
872 HexString.toHexString(dpid));
873 return;
874 }
875
876 flowPusher.add(sw, po);
877 }
878
879 private String inetAddressToString(byte[] bytes) {
880 try {
881 return InetAddress.getByAddress(bytes).getHostAddress();
882 } catch (UnknownHostException e) {
883 log.debug("Invalid IP address", e);
884 return "";
885 }
886 }
887
888 /*
889 * IProxyArpService methods
890 */
891
892 @Override
893 public MACAddress getMacAddress(InetAddress ipAddress) {
894 // return arpCache.lookup(ipAddress);
895 return null;
896 }
897
898 @Override
899 public void sendArpRequest(InetAddress ipAddress, IArpRequester requester,
Ray Milkey269ffb92014-04-03 14:43:30 -0700900 boolean retry) {
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700901 arpRequests.put(ipAddress, new ArpRequest(requester, retry));
902
903 // Sanity check to make sure we don't send a request for our own address
904 if (!configService.isInterfaceAddress(ipAddress)) {
905 sendArpRequestForAddress(ipAddress);
906 }
907 }
908
909 @Override
910 public List<String> getMappings() {
911 return new ArrayList<String>();
912 }
913
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700914 private void sendArpReplyToWaitingRequesters(InetAddress address,
Ray Milkey269ffb92014-04-03 14:43:30 -0700915 MACAddress mac) {
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700916 log.debug("Sending ARP reply for {} to requesters",
917 address.getHostAddress());
918
919 // See if anyone's waiting for this ARP reply
920 Set<ArpRequest> requests = arpRequests.get(address);
921
922 // Synchronize on the Multimap while using an iterator for one of the
923 // sets
924 List<ArpRequest> requestsToSend = new ArrayList<ArpRequest>(
925 requests.size());
926 synchronized (arpRequests) {
927 Iterator<ArpRequest> it = requests.iterator();
928 while (it.hasNext()) {
929 ArpRequest request = it.next();
930 it.remove();
931 requestsToSend.add(request);
932 }
933 }
934
TeruU7feef8a2014-04-03 00:15:49 -0700935 //TODO here, comment outed from long time ago. I will check if we need it later.
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700936 /*IDeviceObject deviceObject = deviceStorage.getDeviceByIP(
Ray Milkey269ffb92014-04-03 14:43:30 -0700937 InetAddresses.coerceToInteger(address));
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700938
939 MACAddress mac = MACAddress.valueOf(deviceObject.getMACAddress());
940
941 log.debug("Found {} at {} in network map",
Ray Milkey269ffb92014-04-03 14:43:30 -0700942 address.getHostAddress(), mac);*/
Jonathan Hart7c9a2fb2014-03-27 09:51:41 -0700943
944 // Don't hold an ARP lock while dispatching requests
945 for (ArpRequest request : requestsToSend) {
946 request.dispatchReply(address, mac);
947 }
948 }
Ray Milkey269ffb92014-04-03 14:43:30 -0700949}