Showing
2 changed files
with
444 additions
and
1 deletions
... | @@ -55,6 +55,7 @@ public class ProxyArpManager implements ProxyArpService { | ... | @@ -55,6 +55,7 @@ public class ProxyArpManager implements ProxyArpService { |
55 | private static final String REQUEST_NULL = "Arp request cannot be null."; | 55 | private static final String REQUEST_NULL = "Arp request cannot be null."; |
56 | private static final String REQUEST_NOT_ARP = "Ethernet frame does not contain ARP request."; | 56 | private static final String REQUEST_NOT_ARP = "Ethernet frame does not contain ARP request."; |
57 | private static final String NOT_ARP_REQUEST = "ARP is not a request."; | 57 | private static final String NOT_ARP_REQUEST = "ARP is not a request."; |
58 | + private static final String NOT_ARP_REPLY = "ARP is not a reply."; | ||
58 | 59 | ||
59 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) | 60 | @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
60 | protected HostService hostService; | 61 | protected HostService hostService; |
... | @@ -141,7 +142,7 @@ public class ProxyArpManager implements ProxyArpService { | ... | @@ -141,7 +142,7 @@ public class ProxyArpManager implements ProxyArpService { |
141 | checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP, | 142 | checkArgument(eth.getEtherType() == Ethernet.TYPE_ARP, |
142 | REQUEST_NOT_ARP); | 143 | REQUEST_NOT_ARP); |
143 | ARP arp = (ARP) eth.getPayload(); | 144 | ARP arp = (ARP) eth.getPayload(); |
144 | - checkArgument(arp.getOpCode() == ARP.OP_REPLY, NOT_ARP_REQUEST); | 145 | + checkArgument(arp.getOpCode() == ARP.OP_REPLY, NOT_ARP_REPLY); |
145 | 146 | ||
146 | Host h = hostService.getHost(HostId.hostId(eth.getDestinationMAC(), | 147 | Host h = hostService.getHost(HostId.hostId(eth.getDestinationMAC(), |
147 | VlanId.vlanId(eth.getVlanID()))); | 148 | VlanId.vlanId(eth.getVlanID()))); | ... | ... |
1 | +package org.onlab.onos.net.proxyarp.impl; | ||
2 | + | ||
3 | +import static org.easymock.EasyMock.anyObject; | ||
4 | +import static org.easymock.EasyMock.createMock; | ||
5 | +import static org.easymock.EasyMock.expect; | ||
6 | +import static org.easymock.EasyMock.replay; | ||
7 | +import static org.junit.Assert.assertEquals; | ||
8 | +import static org.junit.Assert.assertFalse; | ||
9 | +import static org.junit.Assert.assertTrue; | ||
10 | + | ||
11 | +import java.util.ArrayList; | ||
12 | +import java.util.Arrays; | ||
13 | +import java.util.Collections; | ||
14 | +import java.util.Comparator; | ||
15 | +import java.util.List; | ||
16 | + | ||
17 | +import org.junit.Before; | ||
18 | +import org.junit.Test; | ||
19 | +import org.onlab.onos.net.ConnectPoint; | ||
20 | +import org.onlab.onos.net.DefaultHost; | ||
21 | +import org.onlab.onos.net.Device; | ||
22 | +import org.onlab.onos.net.DeviceId; | ||
23 | +import org.onlab.onos.net.Host; | ||
24 | +import org.onlab.onos.net.HostId; | ||
25 | +import org.onlab.onos.net.HostLocation; | ||
26 | +import org.onlab.onos.net.Link; | ||
27 | +import org.onlab.onos.net.Port; | ||
28 | +import org.onlab.onos.net.PortNumber; | ||
29 | +import org.onlab.onos.net.device.DeviceListener; | ||
30 | +import org.onlab.onos.net.device.DeviceService; | ||
31 | +import org.onlab.onos.net.flow.instructions.Instruction; | ||
32 | +import org.onlab.onos.net.flow.instructions.Instructions.OutputInstruction; | ||
33 | +import org.onlab.onos.net.host.HostService; | ||
34 | +import org.onlab.onos.net.link.LinkListener; | ||
35 | +import org.onlab.onos.net.link.LinkService; | ||
36 | +import org.onlab.onos.net.packet.OutboundPacket; | ||
37 | +import org.onlab.onos.net.packet.PacketProcessor; | ||
38 | +import org.onlab.onos.net.packet.PacketService; | ||
39 | +import org.onlab.onos.net.provider.ProviderId; | ||
40 | +import org.onlab.packet.ARP; | ||
41 | +import org.onlab.packet.Ethernet; | ||
42 | +import org.onlab.packet.IpPrefix; | ||
43 | +import org.onlab.packet.MacAddress; | ||
44 | +import org.onlab.packet.VlanId; | ||
45 | + | ||
46 | +import com.google.common.collect.Sets; | ||
47 | + | ||
48 | +/** | ||
49 | + * Tests for the {@link ProxyArpManager} class. | ||
50 | + */ | ||
51 | +public class ProxyArpManagerTest { | ||
52 | + | ||
53 | + private static final int NUM_DEVICES = 4; | ||
54 | + private static final int NUM_PORTS_PER_DEVICE = 3; | ||
55 | + private static final int NUM_FLOOD_PORTS = 4; | ||
56 | + | ||
57 | + private static final IpPrefix IP1 = IpPrefix.valueOf("10.0.0.1/24"); | ||
58 | + private static final IpPrefix IP2 = IpPrefix.valueOf("10.0.0.2/24"); | ||
59 | + | ||
60 | + private static final ProviderId PID = new ProviderId("of", "foo"); | ||
61 | + | ||
62 | + private static final VlanId VLAN1 = VlanId.vlanId((short) 1); | ||
63 | + private static final VlanId VLAN2 = VlanId.vlanId((short) 2); | ||
64 | + private static final MacAddress MAC1 = MacAddress.valueOf("00:00:11:00:00:01"); | ||
65 | + private static final MacAddress MAC2 = MacAddress.valueOf("00:00:22:00:00:02"); | ||
66 | + private static final HostId HID1 = HostId.hostId(MAC1, VLAN1); | ||
67 | + private static final HostId HID2 = HostId.hostId(MAC2, VLAN1); | ||
68 | + | ||
69 | + private static final DeviceId DID1 = getDeviceId(1); | ||
70 | + private static final DeviceId DID2 = getDeviceId(2); | ||
71 | + private static final PortNumber P1 = PortNumber.portNumber(1); | ||
72 | + private static final HostLocation LOC1 = new HostLocation(DID1, P1, 123L); | ||
73 | + private static final HostLocation LOC2 = new HostLocation(DID2, P1, 123L); | ||
74 | + | ||
75 | + private ProxyArpManager proxyArp; | ||
76 | + | ||
77 | + private TestPacketService packetService; | ||
78 | + | ||
79 | + private DeviceService deviceService; | ||
80 | + private LinkService linkService; | ||
81 | + private HostService hostService; | ||
82 | + | ||
83 | + @Before | ||
84 | + public void setUp() throws Exception { | ||
85 | + proxyArp = new ProxyArpManager(); | ||
86 | + packetService = new TestPacketService(); | ||
87 | + proxyArp.packetService = packetService; | ||
88 | + | ||
89 | + // Create a host service mock here. Must be replayed by tests once the | ||
90 | + // expectations have been set up | ||
91 | + hostService = createMock(HostService.class); | ||
92 | + proxyArp.hostService = hostService; | ||
93 | + | ||
94 | + createTopology(); | ||
95 | + proxyArp.deviceService = deviceService; | ||
96 | + proxyArp.linkService = linkService; | ||
97 | + | ||
98 | + proxyArp.activate(); | ||
99 | + } | ||
100 | + | ||
101 | + /** | ||
102 | + * Creates a fake topology to feed into the ARP module. | ||
103 | + * <p/> | ||
104 | + * The default topology is a unidirectional ring topology. Each switch has | ||
105 | + * 3 ports. Ports 2 and 3 have the links to neighbor switches, and port 1 | ||
106 | + * is free (edge port). | ||
107 | + */ | ||
108 | + private void createTopology() { | ||
109 | + deviceService = createMock(DeviceService.class); | ||
110 | + linkService = createMock(LinkService.class); | ||
111 | + | ||
112 | + deviceService.addListener(anyObject(DeviceListener.class)); | ||
113 | + linkService.addListener(anyObject(LinkListener.class)); | ||
114 | + | ||
115 | + createDevices(NUM_DEVICES, NUM_PORTS_PER_DEVICE); | ||
116 | + createLinks(NUM_DEVICES); | ||
117 | + } | ||
118 | + | ||
119 | + /** | ||
120 | + * Creates the devices for the fake topology. | ||
121 | + */ | ||
122 | + private void createDevices(int numDevices, int numPorts) { | ||
123 | + List<Device> devices = new ArrayList<>(); | ||
124 | + | ||
125 | + for (int i = 1; i <= numDevices; i++) { | ||
126 | + DeviceId devId = getDeviceId(i); | ||
127 | + Device device = createMock(Device.class); | ||
128 | + expect(device.id()).andReturn(devId).anyTimes(); | ||
129 | + replay(device); | ||
130 | + | ||
131 | + devices.add(device); | ||
132 | + | ||
133 | + List<Port> ports = new ArrayList<>(); | ||
134 | + for (int j = 1; j <= numPorts; j++) { | ||
135 | + Port port = createMock(Port.class); | ||
136 | + expect(port.number()).andReturn(PortNumber.portNumber(j)).anyTimes(); | ||
137 | + replay(port); | ||
138 | + ports.add(port); | ||
139 | + } | ||
140 | + | ||
141 | + expect(deviceService.getPorts(devId)).andReturn(ports); | ||
142 | + } | ||
143 | + | ||
144 | + expect(deviceService.getDevices()).andReturn(devices); | ||
145 | + replay(deviceService); | ||
146 | + } | ||
147 | + | ||
148 | + /** | ||
149 | + * Creates the links for the fake topology. | ||
150 | + * NB: Only unidirectional links are created, as for this purpose all we | ||
151 | + * need is to occupy the ports with some link. | ||
152 | + */ | ||
153 | + private void createLinks(int numDevices) { | ||
154 | + List<Link> links = new ArrayList<Link>(); | ||
155 | + | ||
156 | + for (int i = 1; i <= numDevices; i++) { | ||
157 | + ConnectPoint src = new ConnectPoint( | ||
158 | + getDeviceId(i), | ||
159 | + PortNumber.portNumber(2)); | ||
160 | + ConnectPoint dst = new ConnectPoint( | ||
161 | + getDeviceId((i + 1 > numDevices) ? 1 : i + 1), | ||
162 | + PortNumber.portNumber(3)); | ||
163 | + | ||
164 | + Link link = createMock(Link.class); | ||
165 | + expect(link.src()).andReturn(src).anyTimes(); | ||
166 | + expect(link.dst()).andReturn(dst).anyTimes(); | ||
167 | + replay(link); | ||
168 | + | ||
169 | + links.add(link); | ||
170 | + } | ||
171 | + | ||
172 | + expect(linkService.getLinks()).andReturn(links).anyTimes(); | ||
173 | + replay(linkService); | ||
174 | + } | ||
175 | + | ||
176 | + /** | ||
177 | + * Tests {@link ProxyArpManager#known(IpPrefix)} in the case where the | ||
178 | + * IP address is not known. | ||
179 | + * Verifies the method returns false. | ||
180 | + */ | ||
181 | + @Test | ||
182 | + public void testNotKnown() { | ||
183 | + expect(hostService.getHostsByIp(IP1)).andReturn(Collections.<Host>emptySet()); | ||
184 | + replay(hostService); | ||
185 | + | ||
186 | + assertFalse(proxyArp.known(IP1)); | ||
187 | + } | ||
188 | + | ||
189 | + /** | ||
190 | + * Tests {@link ProxyArpManager#known(IpPrefix)} in the case where the | ||
191 | + * IP address is known. | ||
192 | + * Verifies the method returns true. | ||
193 | + */ | ||
194 | + @Test | ||
195 | + public void testKnown() { | ||
196 | + Host host1 = createMock(Host.class); | ||
197 | + Host host2 = createMock(Host.class); | ||
198 | + | ||
199 | + expect(hostService.getHostsByIp(IP1)) | ||
200 | + .andReturn(Sets.newHashSet(host1, host2)); | ||
201 | + replay(hostService); | ||
202 | + | ||
203 | + assertTrue(proxyArp.known(IP1)); | ||
204 | + } | ||
205 | + | ||
206 | + /** | ||
207 | + * Tests {@link ProxyArpManager#reply(Ethernet)} in the case where the | ||
208 | + * destination host is known. | ||
209 | + * Verifies the correct ARP reply is sent out the correct port. | ||
210 | + */ | ||
211 | + @Test | ||
212 | + public void testReplyKnown() { | ||
213 | + Host replyer = new DefaultHost(PID, HID1, MAC1, VLAN1, LOC2, | ||
214 | + Collections.singleton(IP1)); | ||
215 | + | ||
216 | + Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC1, | ||
217 | + Collections.singleton(IP2)); | ||
218 | + | ||
219 | + expect(hostService.getHostsByIp(IpPrefix.valueOf(IP1.toOctets()))) | ||
220 | + .andReturn(Collections.singleton(replyer)); | ||
221 | + expect(hostService.getHost(HID2)).andReturn(requestor); | ||
222 | + | ||
223 | + replay(hostService); | ||
224 | + | ||
225 | + Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, IP2, IP1); | ||
226 | + | ||
227 | + proxyArp.reply(arpRequest); | ||
228 | + | ||
229 | + assertEquals(1, packetService.packets.size()); | ||
230 | + Ethernet arpReply = buildArp(ARP.OP_REPLY, MAC1, MAC2, IP1, IP2); | ||
231 | + verifyPacketOut(arpReply, LOC1, packetService.packets.get(0)); | ||
232 | + } | ||
233 | + | ||
234 | + /** | ||
235 | + * Tests {@link ProxyArpManager#reply(Ethernet)} in the case where the | ||
236 | + * destination host is not known. | ||
237 | + * Verifies the ARP request is flooded out the correct edge ports. | ||
238 | + */ | ||
239 | + @Test | ||
240 | + public void testReplyUnknown() { | ||
241 | + Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC1, | ||
242 | + Collections.singleton(IP2)); | ||
243 | + | ||
244 | + expect(hostService.getHostsByIp(IpPrefix.valueOf(IP1.toOctets()))) | ||
245 | + .andReturn(Collections.<Host>emptySet()); | ||
246 | + expect(hostService.getHost(HID2)).andReturn(requestor); | ||
247 | + | ||
248 | + replay(hostService); | ||
249 | + | ||
250 | + Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, IP2, IP1); | ||
251 | + | ||
252 | + proxyArp.reply(arpRequest); | ||
253 | + | ||
254 | + verifyFlood(arpRequest); | ||
255 | + } | ||
256 | + | ||
257 | + /** | ||
258 | + * Tests {@link ProxyArpManager#reply(Ethernet)} in the case where the | ||
259 | + * destination host is known for that IP address, but is not on the same | ||
260 | + * VLAN as the source host. | ||
261 | + * Verifies the ARP request is flooded out the correct edge ports. | ||
262 | + */ | ||
263 | + @Test | ||
264 | + public void testReplyDifferentVlan() { | ||
265 | + Host replyer = new DefaultHost(PID, HID1, MAC1, VLAN2, LOC2, | ||
266 | + Collections.singleton(IP1)); | ||
267 | + | ||
268 | + Host requestor = new DefaultHost(PID, HID2, MAC2, VLAN1, LOC1, | ||
269 | + Collections.singleton(IP2)); | ||
270 | + | ||
271 | + expect(hostService.getHostsByIp(IpPrefix.valueOf(IP1.toOctets()))) | ||
272 | + .andReturn(Collections.singleton(replyer)); | ||
273 | + expect(hostService.getHost(HID2)).andReturn(requestor); | ||
274 | + | ||
275 | + replay(hostService); | ||
276 | + | ||
277 | + Ethernet arpRequest = buildArp(ARP.OP_REQUEST, MAC2, null, IP2, IP1); | ||
278 | + | ||
279 | + proxyArp.reply(arpRequest); | ||
280 | + | ||
281 | + verifyFlood(arpRequest); | ||
282 | + } | ||
283 | + | ||
284 | + /** | ||
285 | + * Tests {@link ProxyArpManager#forward(Ethernet)} in the case where the | ||
286 | + * destination host is known. | ||
287 | + * Verifies the correct ARP request is sent out the correct port. | ||
288 | + */ | ||
289 | + @Test | ||
290 | + public void testForwardToHost() { | ||
291 | + Host host1 = new DefaultHost(PID, HID1, MAC1, VLAN1, LOC1, | ||
292 | + Collections.singleton(IP1)); | ||
293 | + | ||
294 | + expect(hostService.getHost(HID1)).andReturn(host1); | ||
295 | + replay(hostService); | ||
296 | + | ||
297 | + Ethernet arpRequest = buildArp(ARP.OP_REPLY, MAC2, MAC1, IP2, IP1); | ||
298 | + | ||
299 | + proxyArp.forward(arpRequest); | ||
300 | + | ||
301 | + assertEquals(1, packetService.packets.size()); | ||
302 | + OutboundPacket packet = packetService.packets.get(0); | ||
303 | + | ||
304 | + verifyPacketOut(arpRequest, LOC1, packet); | ||
305 | + } | ||
306 | + | ||
307 | + /** | ||
308 | + * Tests {@link ProxyArpManager#forward(Ethernet)} in the case where the | ||
309 | + * destination host is not known. | ||
310 | + * Verifies the correct ARP request is flooded out the correct edge ports. | ||
311 | + */ | ||
312 | + @Test | ||
313 | + public void testForwardFlood() { | ||
314 | + expect(hostService.getHost(HID1)).andReturn(null); | ||
315 | + replay(hostService); | ||
316 | + | ||
317 | + Ethernet arpRequest = buildArp(ARP.OP_REPLY, MAC2, MAC1, IP2, IP1); | ||
318 | + | ||
319 | + proxyArp.forward(arpRequest); | ||
320 | + | ||
321 | + verifyFlood(arpRequest); | ||
322 | + } | ||
323 | + | ||
324 | + /** | ||
325 | + * Verifies that the given packet was flooded out all available edge ports. | ||
326 | + * | ||
327 | + * @param packet the packet that was expected to be flooded | ||
328 | + */ | ||
329 | + private void verifyFlood(Ethernet packet) { | ||
330 | + assertEquals(NUM_FLOOD_PORTS, packetService.packets.size()); | ||
331 | + | ||
332 | + Collections.sort(packetService.packets, | ||
333 | + new Comparator<OutboundPacket>() { | ||
334 | + @Override | ||
335 | + public int compare(OutboundPacket o1, OutboundPacket o2) { | ||
336 | + return o1.sendThrough().uri().compareTo(o2.sendThrough().uri()); | ||
337 | + } | ||
338 | + }); | ||
339 | + | ||
340 | + for (int i = 0; i < NUM_FLOOD_PORTS; i++) { | ||
341 | + ConnectPoint cp = new ConnectPoint(getDeviceId(i + 1), PortNumber.portNumber(1)); | ||
342 | + | ||
343 | + OutboundPacket outboundPacket = packetService.packets.get(i); | ||
344 | + verifyPacketOut(packet, cp, outboundPacket); | ||
345 | + } | ||
346 | + } | ||
347 | + | ||
348 | + /** | ||
349 | + * Verifies the given packet was sent out the given port. | ||
350 | + * | ||
351 | + * @param expected the packet that was expected to be sent | ||
352 | + * @param outPort the port the packet was expected to be sent out | ||
353 | + * @param actual the actual OutboundPacket to verify | ||
354 | + */ | ||
355 | + private void verifyPacketOut(Ethernet expected, ConnectPoint outPort, | ||
356 | + OutboundPacket actual) { | ||
357 | + assertTrue(Arrays.equals(expected.serialize(), actual.data().array())); | ||
358 | + assertEquals(1, actual.treatment().instructions().size()); | ||
359 | + assertEquals(outPort.deviceId(), actual.sendThrough()); | ||
360 | + Instruction instruction = actual.treatment().instructions().get(0); | ||
361 | + assertTrue(instruction instanceof OutputInstruction); | ||
362 | + assertEquals(outPort.port(), ((OutputInstruction) instruction).port()); | ||
363 | + } | ||
364 | + | ||
365 | + /** | ||
366 | + * Returns the device ID of the ith device. | ||
367 | + * | ||
368 | + * @param i device to get the ID of | ||
369 | + * @return the device ID | ||
370 | + */ | ||
371 | + private static DeviceId getDeviceId(int i) { | ||
372 | + return DeviceId.deviceId("" + i); | ||
373 | + } | ||
374 | + | ||
375 | + /** | ||
376 | + * Builds an ARP packet with the given parameters. | ||
377 | + * | ||
378 | + * @param opcode opcode of the ARP packet | ||
379 | + * @param srcMac source MAC address | ||
380 | + * @param dstMac destination MAC address, or null if this is a request | ||
381 | + * @param srcIp source IP address | ||
382 | + * @param dstIp destination IP address | ||
383 | + * @return the ARP packet | ||
384 | + */ | ||
385 | + private Ethernet buildArp(short opcode, MacAddress srcMac, MacAddress dstMac, | ||
386 | + IpPrefix srcIp, IpPrefix dstIp) { | ||
387 | + Ethernet eth = new Ethernet(); | ||
388 | + | ||
389 | + if (dstMac == null) { | ||
390 | + eth.setDestinationMACAddress(MacAddress.BROADCAST_MAC); | ||
391 | + } else { | ||
392 | + eth.setDestinationMACAddress(dstMac.getAddress()); | ||
393 | + } | ||
394 | + | ||
395 | + eth.setSourceMACAddress(srcMac.getAddress()); | ||
396 | + eth.setEtherType(Ethernet.TYPE_ARP); | ||
397 | + eth.setVlanID(VLAN1.toShort()); | ||
398 | + | ||
399 | + ARP arp = new ARP(); | ||
400 | + arp.setOpCode(opcode); | ||
401 | + arp.setProtocolType(ARP.PROTO_TYPE_IP); | ||
402 | + arp.setHardwareType(ARP.HW_TYPE_ETHERNET); | ||
403 | + | ||
404 | + arp.setProtocolAddressLength((byte) IpPrefix.INET_LEN); | ||
405 | + arp.setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH); | ||
406 | + arp.setSenderHardwareAddress(srcMac.getAddress()); | ||
407 | + | ||
408 | + if (dstMac == null) { | ||
409 | + arp.setTargetHardwareAddress(MacAddress.ZERO_MAC_ADDRESS); | ||
410 | + } else { | ||
411 | + arp.setTargetHardwareAddress(dstMac.getAddress()); | ||
412 | + } | ||
413 | + | ||
414 | + arp.setSenderProtocolAddress(srcIp.toOctets()); | ||
415 | + arp.setTargetProtocolAddress(dstIp.toOctets()); | ||
416 | + | ||
417 | + eth.setPayload(arp); | ||
418 | + return eth; | ||
419 | + } | ||
420 | + | ||
421 | + /** | ||
422 | + * Test PacketService implementation that simply stores OutboundPackets | ||
423 | + * passed to {@link #emit(OutboundPacket)} for later verification. | ||
424 | + */ | ||
425 | + class TestPacketService implements PacketService { | ||
426 | + | ||
427 | + List<OutboundPacket> packets = new ArrayList<>(); | ||
428 | + | ||
429 | + @Override | ||
430 | + public void addProcessor(PacketProcessor processor, int priority) { | ||
431 | + } | ||
432 | + | ||
433 | + @Override | ||
434 | + public void removeProcessor(PacketProcessor processor) { | ||
435 | + } | ||
436 | + | ||
437 | + @Override | ||
438 | + public void emit(OutboundPacket packet) { | ||
439 | + packets.add(packet); | ||
440 | + } | ||
441 | + } | ||
442 | +} |
-
Please register or login to post a comment