blob: ddd4827ac59ce643e4985d84e47ba75532109006 [file] [log] [blame]
Jonathan Hart704ca142014-10-09 09:34:39 -07001package org.onlab.onos.net.proxyarp.impl;
2
3import static org.easymock.EasyMock.anyObject;
4import static org.easymock.EasyMock.createMock;
5import static org.easymock.EasyMock.expect;
6import static org.easymock.EasyMock.replay;
7import static org.junit.Assert.assertEquals;
8import static org.junit.Assert.assertFalse;
9import static org.junit.Assert.assertTrue;
10
11import java.util.ArrayList;
12import java.util.Arrays;
13import java.util.Collections;
14import java.util.Comparator;
15import java.util.List;
16
17import org.junit.Before;
18import org.junit.Test;
19import org.onlab.onos.net.ConnectPoint;
20import org.onlab.onos.net.DefaultHost;
21import org.onlab.onos.net.Device;
22import org.onlab.onos.net.DeviceId;
23import org.onlab.onos.net.Host;
24import org.onlab.onos.net.HostId;
25import org.onlab.onos.net.HostLocation;
26import org.onlab.onos.net.Link;
27import org.onlab.onos.net.Port;
28import org.onlab.onos.net.PortNumber;
29import org.onlab.onos.net.device.DeviceListener;
30import org.onlab.onos.net.device.DeviceService;
31import org.onlab.onos.net.flow.instructions.Instruction;
32import org.onlab.onos.net.flow.instructions.Instructions.OutputInstruction;
33import org.onlab.onos.net.host.HostService;
34import org.onlab.onos.net.link.LinkListener;
35import org.onlab.onos.net.link.LinkService;
36import org.onlab.onos.net.packet.OutboundPacket;
37import org.onlab.onos.net.packet.PacketProcessor;
38import org.onlab.onos.net.packet.PacketService;
39import org.onlab.onos.net.provider.ProviderId;
40import org.onlab.packet.ARP;
41import org.onlab.packet.Ethernet;
42import org.onlab.packet.IpPrefix;
43import org.onlab.packet.MacAddress;
44import org.onlab.packet.VlanId;
45
46import com.google.common.collect.Sets;
47
48/**
49 * Tests for the {@link ProxyArpManager} class.
50 */
51public 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}