TEST(ArpTest, ArpTableSerialization) { auto sw = setupSwitch(); VlanID vlanID(1); IPAddressV4 senderIP = IPAddressV4("10.0.0.1"); IPAddressV4 targetIP = IPAddressV4("10.0.0.2"); EXPECT_HW_CALL(sw, stateChanged(_)).Times(0); auto vlan = sw->getState()->getVlans()->getVlanIf(vlanID); EXPECT_NE(vlan, nullptr); auto arpTable = vlan->getArpTable(); EXPECT_NE(arpTable, nullptr); EXPECT_EQ(arpTable->hasPendingEntries(), false); auto serializedArpTable = arpTable->toFollyDynamic(); auto unserializedArpTable = arpTable->fromFollyDynamic(serializedArpTable); EXPECT_EQ(unserializedArpTable->hasPendingEntries(), false); waitForStateUpdates(sw.get()); // Cache the current stats CounterCache counters(sw.get()); testSendArpRequest(sw, vlanID, senderIP, targetIP); waitForStateUpdates(sw.get()); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); vlan = sw->getState()->getVlans()->getVlanIf(vlanID); EXPECT_NE(vlan, nullptr); arpTable = vlan->getArpTable(); EXPECT_NE(arpTable, nullptr); EXPECT_EQ(arpTable->hasPendingEntries(), true); serializedArpTable = arpTable->toFollyDynamic(); unserializedArpTable = arpTable->fromFollyDynamic(serializedArpTable); // Pending flag should still be set EXPECT_EQ(unserializedArpTable->hasPendingEntries(), true); // Should also see a pending entry auto entry = sw->getState()->getVlans()->getVlanIf(vlanID)->getArpTable() ->getEntryIf(targetIP); EXPECT_NE(sw, nullptr); }
TEST(ArpTest, PendingArpCleanup) { std::chrono::seconds arpTimeout(1); std::chrono::seconds arpAgerInterval(1); auto sw = setupSwitch(arpTimeout, arpAgerInterval); VlanID vlanID(1); IPAddressV4 senderIP = IPAddressV4("10.0.0.1"); IPAddressV4 targetIP = IPAddressV4("10.0.0.2"); // Cache the current stats CounterCache counters(sw.get()); testSendArpRequest(sw, vlanID, senderIP, targetIP); waitForStateUpdates(sw.get()); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); // Should see a pending entry now auto entry = sw->getState()->getVlans()->getVlanIf(vlanID)->getArpTable() ->getEntryIf(targetIP); EXPECT_NE(entry, nullptr); EXPECT_EQ(entry->isPending(), true); // Send an Arp request for a different neighbor IPAddressV4 targetIP2 = IPAddressV4("10.0.0.3"); testSendArpRequest(sw, vlanID, senderIP, targetIP2); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); // Should see another pending entry now waitForStateUpdates(sw.get()); entry = sw->getState()->getVlans()->getVlanIf(vlanID)->getArpTable() ->getEntryIf(targetIP2); EXPECT_NE(entry, nullptr); EXPECT_EQ(entry->isPending(), true); }
TEST(ArpTest, PendingArp) { auto sw = setupSwitch(); VlanID vlanID(1); IPAddressV4 senderIP = IPAddressV4("10.0.0.1"); IPAddressV4 targetIP = IPAddressV4("10.0.0.2"); // Cache the current stats CounterCache counters(sw.get()); testSendArpRequest(sw, vlanID, senderIP, targetIP); waitForStateUpdates(sw.get()); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); // Create an IP pkt for 10.0.0.10 auto pkt = MockRxPacket::fromHex( // dst mac, src mac "02 00 01 00 00 01 02 00 02 01 02 03" // 802.1q, VLAN 1 "81 00 00 01" // IPv4 "08 00" // Version(4), IHL(5), DSCP(0), ECN(0), Total Length(20) "45 00 00 14" // Identification(0), Flags(0), Fragment offset(0) "00 00 00 00" // TTL(31), Protocol(6), Checksum (0, fake) "1F 06 00 00" // Source IP (1.2.3.4) "01 02 03 04" // Destination IP (10.0.0.10) "0a 00 00 0a" ); pkt->padToLength(68); pkt->setSrcPort(PortID(1)); pkt->setSrcVlan(vlanID); // Receiving this packet should trigger an ARP request out, // and the state should now include a pending arp entry. EXPECT_HW_CALL(sw, stateChanged(_)).Times(1); EXPECT_PKT(sw, "ARP request", checkArpRequest(senderIP, MacAddress("00:02:00:00:00:01"), IPAddressV4("10.0.0.10"), vlanID)); sw->packetReceived(pkt->clone()); // Should see a pending entry now waitForStateUpdates(sw.get()); auto entry = sw->getState()->getVlans()->getVlanIf(vlanID)->getArpTable() ->getEntryIf(IPAddressV4("10.0.0.10")); EXPECT_NE(entry, nullptr); EXPECT_EQ(entry->isPending(), true); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.pkts.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.arp.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.drops.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.error.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.ipv4.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "ipv4.nexthop.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "ipv4.no_arp.sum", 0); // Receiving this duplicate packet should NOT trigger an ARP request out, // and no state update for now. EXPECT_HW_CALL(sw, stateChanged(_)).Times(0); sw->packetReceived(pkt->clone()); // Should still see a pending entry now waitForStateUpdates(sw.get()); entry = sw->getState()->getVlans()->getVlanIf(vlanID)->getArpTable() ->getEntryIf(IPAddressV4("10.0.0.10")); EXPECT_NE(entry, nullptr); EXPECT_EQ(entry->isPending(), true); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.pkts.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.arp.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.drops.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.error.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.ipv4.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "ipv4.nexthop.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "ipv4.no_arp.sum", 0); EXPECT_HW_CALL(sw, stateChanged(_)).Times(1); // Receive an arp reply for our pending entry sendArpReply(sw.get(), "10.0.0.10", "02:10:20:30:40:22", 1); // The entry should now be valid instead of pending waitForStateUpdates(sw.get()); entry = sw->getState()->getVlans()->getVlanIf(vlanID)->getArpTable() ->getEntryIf(IPAddressV4("10.0.0.10")); EXPECT_NE(entry, nullptr); EXPECT_EQ(entry->isPending(), false); // Verify that we don't ever overwrite a valid entry with a pending one. // Receive the same packet again, no state update and the entry should still // be valid EXPECT_HW_CALL(sw, stateChanged(_)).Times(0); sw->packetReceived(pkt->clone()); waitForStateUpdates(sw.get()); entry = sw->getState()->getVlans()->getVlanIf(vlanID)->getArpTable() ->getEntryIf(IPAddressV4("10.0.0.10")); EXPECT_NE(entry, nullptr); EXPECT_EQ(entry->isPending(), false); };
TEST(ArpTest, FlushEntry) { auto sw = setupSwitch(); // Send ARP replies from several nodes to populate the table EXPECT_HW_CALL(sw, stateChanged(_)).Times(testing::AtLeast(1)); sendArpReply(sw.get(), "10.0.0.11", "02:10:20:30:40:11", 2); sendArpReply(sw.get(), "10.0.0.15", "02:10:20:30:40:15", 3); sendArpReply(sw.get(), "10.0.0.7", "02:10:20:30:40:07", 1); sendArpReply(sw.get(), "10.0.0.22", "02:10:20:30:40:22", 4); // Wait for all state updates from the ARP replies to be applied waitForStateUpdates(sw.get()); // Use the thrift API to confirm that the ARP table is populated as expected. // This also serves to test the thrift handler functionality. ThriftHandler thriftHandler(sw.get()); std::vector<ArpEntryThrift> arpTable; thriftHandler.getArpTable(arpTable); ASSERT_EQ(arpTable.size(), 4); // The results should always be sorted first by VLAN and then by IP, // so we can check the exact ordering here. auto checkEntry = [&](int idx, StringPiece ip, StringPiece mac, int port) { SCOPED_TRACE(folly::to<string>("index ", idx)); EXPECT_EQ(arpTable[idx].vlanID, 1); EXPECT_EQ(arpTable[idx].vlanName, "Vlan1"); EXPECT_EQ(toIPAddress(arpTable[idx].ip).str(), ip); EXPECT_EQ(arpTable[idx].mac, mac); EXPECT_EQ(arpTable[idx].port, port); }; checkEntry(0, "10.0.0.7", "02:10:20:30:40:07", 1); checkEntry(1, "10.0.0.11", "02:10:20:30:40:11", 2); checkEntry(2, "10.0.0.15", "02:10:20:30:40:15", 3); checkEntry(3, "10.0.0.22", "02:10:20:30:40:22", 4); // Via the thrift API, flush the ARP entry for 10.0.0.11 EXPECT_HW_CALL(sw, stateChanged(_)).Times(1); auto binAddr = toBinaryAddress(IPAddressV4("10.0.0.11")); auto numFlushed = thriftHandler.flushNeighborEntry( make_unique<BinaryAddress>(binAddr), 1); EXPECT_EQ(numFlushed, 1); // Now check the table again arpTable.clear(); thriftHandler.getArpTable(arpTable); ASSERT_EQ(arpTable.size(), 3); checkEntry(0, "10.0.0.7", "02:10:20:30:40:07", 1); checkEntry(1, "10.0.0.15", "02:10:20:30:40:15", 3); checkEntry(2, "10.0.0.22", "02:10:20:30:40:22", 4); // Calling flushNeighborEntry() with an IP that isn't present in the table // should do nothing and return 0 EXPECT_HW_CALL(sw, stateChanged(_)).Times(0); binAddr = toBinaryAddress(IPAddressV4("10.0.0.254")); numFlushed = thriftHandler.flushNeighborEntry( make_unique<BinaryAddress>(binAddr), 1); EXPECT_EQ(numFlushed, 0); binAddr = toBinaryAddress(IPAddressV4("1.2.3.4")); numFlushed = thriftHandler.flushNeighborEntry( make_unique<BinaryAddress>(binAddr), 1); EXPECT_EQ(numFlushed, 0); // Calling flushNeighborEntry() with a bogus VLAN ID should throw an error. binAddr = toBinaryAddress(IPAddressV4("1.2.3.4")); auto binAddrPtr = make_unique<BinaryAddress>(binAddr); EXPECT_THROW(thriftHandler.flushNeighborEntry(std::move(binAddrPtr), 123), FbossError); }
TEST(ArpTest, TableUpdates) { auto sw = setupSwitch(); VlanID vlanID(1); // Create an ARP request for 10.0.0.1 from an unreachable source auto pkt = MockRxPacket::fromHex( // dst mac, src mac "ff ff ff ff ff ff 00 02 00 01 02 03" // 802.1q, VLAN 1 "81 00 00 01" // ARP, htype: ethernet, ptype: IPv4, hlen: 6, plen: 4 "08 06 00 01 08 00 06 04" // ARP Request "00 01" // Sender MAC "00 02 00 01 02 03" // Sender IP: 10.0.1.15 "0a 00 01 0f" // Target MAC "00 00 00 00 00 00" // Target IP: 10.0.0.1 "0a 00 00 01" ); pkt->padToLength(68); pkt->setSrcPort(PortID(1)); pkt->setSrcVlan(vlanID); // Cache the current stats CounterCache counters(sw.get()); // Sending the ARP request to the switch should not trigger an update to the // ArpTable for VLAN 1, but will send a reply packet EXPECT_HW_CALL(sw, stateChanged(_)).Times(0); EXPECT_PKT(sw, "ARP reply", checkArpReply("10.0.0.1", "00:02:00:00:00:01", "10.0.1.15", "00:02:00:01:02:03", vlanID)); // Inform the SwSwitch of the ARP request sw->packetReceived(pkt->clone()); // Check the new ArpTable does not have any entry auto arpTable = sw->getState()->getVlans()->getVlan(vlanID)->getArpTable(); EXPECT_EQ(0, arpTable->getAllNodes().size()); // Create an ARP request for 10.0.0.1 pkt = MockRxPacket::fromHex( // dst mac, src mac "ff ff ff ff ff ff 00 02 00 01 02 03" // 802.1q, VLAN 1 "81 00 00 01" // ARP, htype: ethernet, ptype: IPv4, hlen: 6, plen: 4 "08 06 00 01 08 00 06 04" // ARP Request "00 01" // Sender MAC "00 02 00 01 02 03" // Sender IP: 10.0.0.15 "0a 00 00 0f" // Target MAC "00 00 00 00 00 00" // Target IP: 10.0.0.1 "0a 00 00 01" ); pkt->padToLength(68); pkt->setSrcPort(PortID(1)); pkt->setSrcVlan(vlanID); // update counters counters.update(); // Sending the ARP request to the switch should trigger an update to the // ArpTable for VLAN 1, and will then send a reply packet EXPECT_HW_CALL(sw, stateChanged(_)); EXPECT_PKT(sw, "ARP reply", checkArpReply("10.0.0.1", "00:02:00:00:00:01", "10.0.0.15", "00:02:00:01:02:03", vlanID)); // Inform the SwSwitch of the ARP request sw->packetReceived(pkt->clone()); // Wait for any updates triggered by the packet to complete. waitForStateUpdates(sw.get()); // Check the new ArpTable contents arpTable = sw->getState()->getVlans()->getVlan(vlanID)->getArpTable(); EXPECT_EQ(1, arpTable->getAllNodes().size()); auto entry = arpTable->getEntry(IPAddressV4("10.0.0.15")); EXPECT_EQ(MacAddress("00:02:00:01:02:03"), entry->getMac()); EXPECT_EQ(PortID(1), entry->getPort()); EXPECT_EQ(InterfaceID(1), entry->getIntfID()); // Check the new stats counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.pkts.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.arp.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.drops.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.error.sum", 0); // Sending the same packet again should generate another response, // but not another table update. EXPECT_HW_CALL(sw, stateChanged(_)).Times(0); EXPECT_PKT(sw, "ARP reply", checkArpReply("10.0.0.1", "00:02:00:00:00:01", "10.0.0.15", "00:02:00:01:02:03", vlanID)); sw->packetReceived(pkt->clone()); // Check the counters again counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.pkts.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.arp.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.drops.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.error.sum", 0); // An ARP request from an unknown node that isn't destined for us shouldn't // generate a reply or a table update. pkt = MockRxPacket::fromHex( // dst mac, src mac "ff ff ff ff ff ff 00 02 00 01 02 04" // 802.1q, VLAN 1 "81 00 00 01" // ARP, htype: ethernet, ptype: IPv4, hlen: 6, plen: 4 "08 06 00 01 08 00 06 04" // ARP Request "00 01" // Sender MAC "00 02 00 01 02 04" // Sender IP: 10.0.0.16 "0a 00 00 10" // Target MAC "00 00 00 00 00 00" // Target IP: 10.0.0.50 "0a 00 00 32" ); pkt->padToLength(68); pkt->setSrcPort(PortID(1)); pkt->setSrcVlan(vlanID); EXPECT_HW_CALL(sw, stateChanged(_)).Times(0); EXPECT_HW_CALL(sw, sendPacketSwitched_(_)).Times(0); sw->packetReceived(pkt->clone()); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.pkts.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.arp.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.not_mine.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.drops.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.error.sum", 0); // A request from a node we already have in the arp table, but isn't for us, // should generate a table update if the MAC is different from what we // already have. pkt = MockRxPacket::fromHex( // dst mac, src mac "ff ff ff ff ff ff 00 02 00 01 02 08" // 802.1q, VLAN 1 "81 00 00 01" // ARP, htype: ethernet, ptype: IPv4, hlen: 6, plen: 4 "08 06 00 01 08 00 06 04" // ARP Request "00 01" // Sender MAC "00 02 00 01 02 08" // Sender IP: 10.0.0.15 "0a 00 00 0f" // Target MAC "00 00 00 00 00 00" // Target IP: 10.0.0.50 "0a 00 00 32" ); pkt->padToLength(68); pkt->setSrcPort(PortID(2)); pkt->setSrcVlan(vlanID); EXPECT_HW_CALL(sw, stateChanged(_)).Times(1); EXPECT_HW_CALL(sw, sendPacketSwitched_(_)).Times(0); sw->packetReceived(pkt->clone()); waitForStateUpdates(sw.get()); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.pkts.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.arp.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.not_mine.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.drops.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.error.sum", 0); arpTable = sw->getState()->getVlans()->getVlan(vlanID)->getArpTable(); EXPECT_EQ(1, arpTable->getAllNodes().size()); entry = arpTable->getEntry(IPAddressV4("10.0.0.15")); EXPECT_EQ(MacAddress("00:02:00:01:02:08"), entry->getMac()); EXPECT_EQ(PortID(2), entry->getPort()); EXPECT_EQ(InterfaceID(1), entry->getIntfID()); // Try another request for us from a node we haven't seen yet. // This should generate a reply and a table update pkt = MockRxPacket::fromHex( // dst mac, src mac "ff ff ff ff ff ff 00 02 00 01 02 23" // 802.1q, VLAN 1 "81 00 00 01" // ARP, htype: ethernet, ptype: IPv4, hlen: 6, plen: 4 "08 06 00 01 08 00 06 04" // ARP Request "00 01" // Sender MAC "00 02 00 01 02 23" // Sender IP: 10.0.0.20 "0a 00 00 14" // Target MAC "00 00 00 00 00 00" // Target IP: 10.0.0.1 "0a 00 00 01" ); pkt->padToLength(68); pkt->setSrcPort(PortID(1)); pkt->setSrcVlan(vlanID); EXPECT_HW_CALL(sw, stateChanged(_)).Times(1); EXPECT_PKT(sw, "ARP reply", checkArpReply("10.0.0.1", "00:02:00:00:00:01", "10.0.0.20", "00:02:00:01:02:23", vlanID)); sw->packetReceived(pkt->clone()); waitForStateUpdates(sw.get()); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.pkts.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.arp.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.drops.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.error.sum", 0); arpTable = sw->getState()->getVlans()->getVlan(vlanID)->getArpTable(); EXPECT_EQ(2, arpTable->getAllNodes().size()); entry = arpTable->getEntry(IPAddressV4("10.0.0.15")); EXPECT_EQ(MacAddress("00:02:00:01:02:08"), entry->getMac()); EXPECT_EQ(PortID(2), entry->getPort()); EXPECT_EQ(InterfaceID(1), entry->getIntfID()); entry = arpTable->getEntry(IPAddressV4("10.0.0.20")); EXPECT_EQ(MacAddress("00:02:00:01:02:23"), entry->getMac()); EXPECT_EQ(PortID(1), entry->getPort()); EXPECT_EQ(InterfaceID(1), entry->getIntfID()); // Try a request for an IP on another interface, to make sure // it generates an ARP entry with the correct interface ID. pkt = MockRxPacket::fromHex( // dst mac, src mac "ff ff ff ff ff ff 00 02 00 55 66 77" // 802.1q, VLAN 1 "81 00 00 01" // ARP, htype: ethernet, ptype: IPv4, hlen: 6, plen: 4 "08 06 00 01 08 00 06 04" // ARP Request "00 01" // Sender MAC "00 02 00 55 66 77" // Sender IP: 192.168.0.20 "c0 a8 00 14" // Target MAC "00 00 00 00 00 00" // Target IP: 192.168.0.1 "c0 a8 00 01" ); pkt->padToLength(68); pkt->setSrcPort(PortID(5)); pkt->setSrcVlan(vlanID); EXPECT_HW_CALL(sw, stateChanged(_)).Times(1); EXPECT_PKT(sw, "ARP reply", checkArpReply("192.168.0.1", "00:02:00:00:00:01", "192.168.0.20", "00:02:00:55:66:77", vlanID)); sw->packetReceived(pkt->clone()); waitForStateUpdates(sw.get()); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.pkts.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.arp.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.drops.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.error.sum", 0); arpTable = sw->getState()->getVlans()->getVlan(vlanID)->getArpTable(); EXPECT_EQ(3, arpTable->getAllNodes().size()); entry = arpTable->getEntry(IPAddressV4("10.0.0.15")); EXPECT_EQ(MacAddress("00:02:00:01:02:08"), entry->getMac()); EXPECT_EQ(PortID(2), entry->getPort()); EXPECT_EQ(InterfaceID(1), entry->getIntfID()); entry = arpTable->getEntry(IPAddressV4("10.0.0.20")); EXPECT_EQ(MacAddress("00:02:00:01:02:23"), entry->getMac()); EXPECT_EQ(PortID(1), entry->getPort()); EXPECT_EQ(InterfaceID(1), entry->getIntfID()); entry = arpTable->getEntry(IPAddressV4("192.168.0.20")); EXPECT_EQ(MacAddress("00:02:00:55:66:77"), entry->getMac()); EXPECT_EQ(PortID(5), entry->getPort()); EXPECT_EQ(InterfaceID(1), entry->getIntfID()); }
TEST(ArpTest, SendRequest) { auto sw = setupSwitch(); VlanID vlanID(1); IPAddressV4 senderIP = IPAddressV4("10.0.0.1"); IPAddressV4 targetIP = IPAddressV4("10.0.0.2"); // Cache the current stats CounterCache counters(sw.get()); testSendArpRequest(sw, vlanID, senderIP, targetIP); waitForStateUpdates(sw.get()); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); // Create an IP pkt for 10.0.0.10 auto pkt = MockRxPacket::fromHex( // dst mac, src mac "02 00 01 00 00 01 02 00 02 01 02 03" // 802.1q, VLAN 1 "81 00 00 01" // IPv4 "08 00" // Version(4), IHL(5), DSCP(0), ECN(0), Total Length(20) "45 00 00 14" // Identification(0), Flags(0), Fragment offset(0) "00 00 00 00" // TTL(31), Protocol(6), Checksum (0, fake) "1F 06 00 00" // Source IP (1.2.3.4) "01 02 03 04" // Destination IP (10.0.0.10) "0a 00 00 0a" ); pkt->padToLength(68); pkt->setSrcPort(PortID(1)); pkt->setSrcVlan(vlanID); // Receiving this packet should trigger an ARP request out. // The state will also update because a pending arp entry will be created EXPECT_HW_CALL(sw, stateChanged(_)).Times(1); EXPECT_PKT(sw, "ARP request", checkArpRequest(senderIP, MacAddress("00:02:00:00:00:01"), IPAddressV4("10.0.0.10"), vlanID)); sw->packetReceived(pkt->clone()); waitForStateUpdates(sw.get()); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.pkts.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.arp.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.drops.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.error.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.ipv4.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "ipv4.nexthop.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "ipv4.no_arp.sum", 0); // Create an IP pkt for 10.0.10.10 pkt = MockRxPacket::fromHex( // dst mac, src mac "02 00 01 00 00 01 02 00 02 01 02 03" // 802.1q, VLAN 1 "81 00 00 01" // IPv4 "08 00" // Version(4), IHL(5), DSCP(0), ECN(0), Total Length(20) "45 00 00 14" // Identification(0), Flags(0), Fragment offset(0) "00 00 00 00" // TTL(31), Protocol(6), Checksum (0, fake) "1F 06 00 00" // Source IP (1.2.3.4) "01 02 03 04" // Destination IP (10.0.10.10) "0a 00 0a 0a" ); pkt->padToLength(68); pkt->setSrcPort(PortID(1)); pkt->setSrcVlan(vlanID); // Receiving this packet should not trigger a ARP request out, // because no interface is able to reach that subnet EXPECT_HW_CALL(sw, stateChanged(_)).Times(0); EXPECT_HW_CALL(sw, sendPacketSwitched_(_)).Times(0); sw->packetReceived(pkt->clone()); waitForStateUpdates(sw.get()); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.pkts.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.arp.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.drops.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.error.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.ipv4.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "ipv4.nexthop.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "ipv4.no_arp.sum", 1); // Create an IP pkt for 10.1.1.10, reachable through 10.0.0.22 and 10.0.0.23 pkt = MockRxPacket::fromHex( // dst mac, src mac "02 00 01 00 00 01 02 00 02 01 02 03" // 802.1q, VLAN 1 "81 00 00 01" // IPv4 "08 00" // Version(4), IHL(5), DSCP(0), ECN(0), Total Length(20) "45 00 00 14" // Identification(0), Flags(0), Fragment offset(0) "00 00 00 00" // TTL(31), Protocol(6), Checksum (0, fake) "1F 06 00 00" // Source IP (1.2.3.4) "01 02 03 04" // Destination IP (10.1.1.10) "0a 01 01 0a" ); pkt->padToLength(68); pkt->setSrcPort(PortID(1)); pkt->setSrcVlan(vlanID); // Receiving this packet should trigger an ARP request to 10.0.0.22 and // 10.0.0.23, which causes pending arp entries to be added to the state. EXPECT_HW_CALL(sw, stateChanged(_)).Times(testing::AtLeast(1)); EXPECT_PKT(sw, "ARP request", checkArpRequest(senderIP, MacAddress("00:02:00:00:00:01"), IPAddressV4("10.0.0.22"), vlanID)); EXPECT_PKT(sw, "ARP request", checkArpRequest(senderIP, MacAddress("00:02:00:00:00:01"), IPAddressV4("10.0.0.23"), vlanID)); sw->packetReceived(pkt->clone()); waitForStateUpdates(sw.get()); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.pkts.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.arp.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 2); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.drops.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.error.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "trapped.ipv4.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "ipv4.nexthop.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "ipv4.no_arp.sum", 0); }
TEST(StaticRoutes, configureUnconfigure) { auto platform = createMockPlatform(); auto stateV0 = make_shared<SwitchState>(); auto tablesV0 = stateV0->getRouteTables(); cfg::SwitchConfig config; config.__isset.staticRoutesToNull = true; config.__isset.staticRoutesToCPU = true; config.staticRoutesToNull.resize(2); config.staticRoutesToNull[0].prefix = "1.1.1.1/32"; config.staticRoutesToNull[1].prefix = "2001::1/128"; config.staticRoutesToCPU.resize(2); config.staticRoutesToCPU[0].prefix = "2.2.2.2/32"; config.staticRoutesToCPU[1].prefix = "2001::2/128"; config.__isset.staticRoutesWithNhops = true; config.staticRoutesWithNhops.resize(4); config.staticRoutesWithNhops[0].prefix = "3.3.3.3/32"; config.staticRoutesWithNhops[0].nexthops.resize(1); config.staticRoutesWithNhops[0].nexthops[0] = "1.1.1.1"; config.staticRoutesWithNhops[1].prefix = "4.4.4.4/32"; config.staticRoutesWithNhops[1].nexthops.resize(1); config.staticRoutesWithNhops[1].nexthops[0] = "2.2.2.2"; // Now add v6 recursive routes config.staticRoutesWithNhops[2].prefix = "2001::3/128"; config.staticRoutesWithNhops[2].nexthops.resize(1); config.staticRoutesWithNhops[2].nexthops[0] = "2001::1"; config.staticRoutesWithNhops[3].prefix = "2001::4/128"; config.staticRoutesWithNhops[3].nexthops.resize(1); config.staticRoutesWithNhops[3].nexthops[0] = "2001::2"; auto stateV1 = publishAndApplyConfig(stateV0, &config, platform.get()); ASSERT_NE(nullptr, stateV1); RouterID rid0(0); auto t1 = stateV1->getRouteTables()->getRouteTableIf(rid0); RouteV4::Prefix prefix1v4{IPAddressV4("1.1.1.1"), 32}; RouteV4::Prefix prefix2v4{IPAddressV4("2.2.2.2"), 32}; RouteV4::Prefix prefix3v4{IPAddressV4("3.3.3.3"), 32}; RouteV4::Prefix prefix4v4{IPAddressV4("4.4.4.4"), 32}; RouteV6::Prefix prefix1v6{IPAddressV6("2001::1"), 128}; RouteV6::Prefix prefix2v6{IPAddressV6("2001::2"), 128}; RouteV6::Prefix prefix3v6{IPAddressV6("2001::3"), 128}; RouteV6::Prefix prefix4v6{IPAddressV6("2001::4"), 128}; auto rib1v4 = t1->getRibV4(); auto r1v4 = rib1v4->exactMatch(prefix1v4); ASSERT_NE(nullptr, r1v4); EXPECT_TRUE(r1v4->isResolved()); EXPECT_FALSE(r1v4->isUnresolvable()); EXPECT_FALSE(r1v4->isConnected()); EXPECT_FALSE(r1v4->needResolve()); EXPECT_EQ( r1v4->getForwardInfo(), RouteNextHopEntry(DROP, AdminDistance::MAX_ADMIN_DISTANCE)); auto r2v4 = rib1v4->exactMatch(prefix2v4); ASSERT_NE(nullptr, r2v4); EXPECT_TRUE(r2v4->isResolved()); EXPECT_FALSE(r2v4->isUnresolvable()); EXPECT_FALSE(r2v4->isConnected()); EXPECT_FALSE(r2v4->needResolve()); EXPECT_EQ( r2v4->getForwardInfo(), RouteNextHopEntry(TO_CPU, AdminDistance::MAX_ADMIN_DISTANCE)); // Recursive resolution to DROP auto r3v4 = rib1v4->exactMatch(prefix3v4); ASSERT_NE(nullptr, r3v4); EXPECT_TRUE(r3v4->isResolved()); EXPECT_FALSE(r3v4->isUnresolvable()); EXPECT_FALSE(r3v4->isConnected()); EXPECT_FALSE(r3v4->needResolve()); EXPECT_EQ( r3v4->getForwardInfo(), RouteNextHopEntry(DROP, AdminDistance::MAX_ADMIN_DISTANCE)); // Recursive resolution to CPU auto r4v4 = rib1v4->exactMatch(prefix4v4); ASSERT_NE(nullptr, r4v4); EXPECT_TRUE(r4v4->isResolved()); EXPECT_FALSE(r4v4->isUnresolvable()); EXPECT_FALSE(r4v4->isConnected()); EXPECT_FALSE(r4v4->needResolve()); EXPECT_EQ( r4v4->getForwardInfo(), RouteNextHopEntry(TO_CPU, AdminDistance::MAX_ADMIN_DISTANCE)); auto rib1v6 = t1->getRibV6(); auto r1v6 = rib1v6->exactMatch(prefix1v6); ASSERT_NE(nullptr, r1v6); EXPECT_TRUE(r1v6->isResolved()); EXPECT_FALSE(r1v6->isUnresolvable()); EXPECT_FALSE(r1v6->isConnected()); EXPECT_FALSE(r1v6->needResolve()); EXPECT_EQ( r1v6->getForwardInfo(), RouteNextHopEntry(DROP, AdminDistance::MAX_ADMIN_DISTANCE)); auto r2v6 = rib1v6->exactMatch(prefix2v6); ASSERT_NE(nullptr, r2v6); EXPECT_TRUE(r2v6->isResolved()); EXPECT_FALSE(r2v6->isUnresolvable()); EXPECT_FALSE(r2v6->isConnected()); EXPECT_FALSE(r2v6->needResolve()); EXPECT_EQ( r2v6->getForwardInfo(), RouteNextHopEntry(TO_CPU, AdminDistance::MAX_ADMIN_DISTANCE)); // Recursive resolution to DROP auto r3v6 = rib1v6->exactMatch(prefix3v6); ASSERT_NE(nullptr, r3v6); EXPECT_TRUE(r3v6->isResolved()); EXPECT_FALSE(r3v6->isUnresolvable()); EXPECT_FALSE(r3v6->isConnected()); EXPECT_FALSE(r3v6->needResolve()); EXPECT_EQ( r3v6->getForwardInfo(), RouteNextHopEntry(DROP, AdminDistance::MAX_ADMIN_DISTANCE)); // Recursive resolution to CPU auto r4v6 = rib1v6->exactMatch(prefix4v6); ASSERT_NE(nullptr, r4v6); EXPECT_TRUE(r4v6->isResolved()); EXPECT_FALSE(r4v6->isUnresolvable()); EXPECT_FALSE(r4v6->isConnected()); EXPECT_FALSE(r4v6->needResolve()); EXPECT_EQ( r4v6->getForwardInfo(), RouteNextHopEntry(TO_CPU, AdminDistance::MAX_ADMIN_DISTANCE)); // Now blow away the static routes from config. cfg::SwitchConfig emptyConfig; auto stateV2 = publishAndApplyConfig(stateV1, &emptyConfig, platform.get(), &config); ASSERT_NE(nullptr, stateV2); auto t2 = stateV2->getRouteTables()->getRouteTableIf(rid0); // No routes and hence no routing table ASSERT_EQ(nullptr, t2); }
TEST(Vlan, applyConfig) { MockPlatform platform; auto stateV0 = make_shared<SwitchState>(); auto vlanV0 = make_shared<Vlan>(VlanID(1234), kVlan1234); stateV0->addVlan(vlanV0); stateV0->registerPort(PortID(1), "port1"); stateV0->registerPort(PortID(99), "port99"); NodeID nodeID = vlanV0->getNodeID(); EXPECT_EQ(0, vlanV0->getGeneration()); EXPECT_FALSE(vlanV0->isPublished()); EXPECT_EQ(VlanID(1234), vlanV0->getID()); Vlan::MemberPorts emptyPorts; EXPECT_EQ(emptyPorts, vlanV0->getPorts()); vlanV0->publish(); EXPECT_TRUE(vlanV0->isPublished()); cfg::SwitchConfig config; config.ports.resize(2); config.ports[0].logicalID = 1; config.ports[0].state = cfg::PortState::UP; config.ports[1].logicalID = 99; config.ports[1].state = cfg::PortState::UP; config.vlans.resize(1); config.vlans[0].id = 1234; config.vlans[0].name = kVlan1234; config.vlans[0].dhcpRelayOverridesV4["02:00:00:00:00:02"] = "1.2.3.4"; config.vlans[0].dhcpRelayOverridesV6["02:00:00:00:00:02"] = "2a03:2880:10:1f07:face:b00c:0:0"; config.vlans[0].intfID = 1; config.vlans[0].__isset.intfID = true; config.vlanPorts.resize(2); config.vlanPorts[0].logicalPort = 1; config.vlanPorts[0].vlanID = 1234; config.vlanPorts[0].emitTags = false; config.vlanPorts[1].logicalPort = 99; config.vlanPorts[1].vlanID = 1234; config.vlanPorts[1].emitTags = true; Vlan::MemberPorts expectedPorts; expectedPorts.insert(make_pair(PortID(1), Vlan::PortInfo(false))); expectedPorts.insert(make_pair(PortID(99), Vlan::PortInfo(true))); auto stateV1 = publishAndApplyConfig(stateV0, &config, &platform); auto vlanV1 = stateV1->getVlans()->getVlan(VlanID(1234)); ASSERT_NE(nullptr, vlanV1); auto vlanV1_byName = stateV1->getVlans()->getVlanSlow(kVlan1234); EXPECT_EQ(vlanV1, vlanV1_byName); EXPECT_EQ(nodeID, vlanV1->getNodeID()); EXPECT_EQ(1, vlanV1->getGeneration()); EXPECT_FALSE(vlanV1->isPublished()); EXPECT_EQ(VlanID(1234), vlanV1->getID()); EXPECT_EQ(kVlan1234, vlanV1->getName()); EXPECT_EQ(expectedPorts, vlanV1->getPorts()); EXPECT_EQ(0, vlanV1->getArpResponseTable()->getTable().size()); EXPECT_EQ(InterfaceID(1), vlanV1->getInterfaceID()); auto map4 = vlanV1->getDhcpV4RelayOverrides(); EXPECT_EQ(IPAddressV4("1.2.3.4"), IPAddressV4(map4[MacAddress("02:00:00:00:00:02")])); auto map6 = vlanV1->getDhcpV6RelayOverrides(); EXPECT_EQ(IPAddressV6("2a03:2880:10:1f07:face:b00c:0:0"), IPAddressV6(map6[MacAddress("02:00:00:00:00:02")])); // Applying the same config again should return null EXPECT_EQ(nullptr, publishAndApplyConfig(stateV1, &config, &platform)); // Add an interface config.interfaces.resize(1); config.interfaces[0].intfID = 1; config.interfaces[0].routerID = 0; config.interfaces[0].vlanID = 1234; config.interfaces[0].ipAddresses.resize(2); config.interfaces[0].ipAddresses[0] = "10.1.1.1/24"; config.interfaces[0].ipAddresses[1] = "2a03:2880:10:1f07:face:b00c:0:0/96"; MacAddress platformMac("82:02:00:ab:cd:ef"); EXPECT_CALL(platform, getLocalMac()).WillRepeatedly(Return(platformMac)); auto stateV2 = publishAndApplyConfig(stateV1, &config, &platform); auto vlanV2 = stateV2->getVlans()->getVlan(VlanID(1234)); EXPECT_EQ(nodeID, vlanV2->getNodeID()); EXPECT_EQ(2, vlanV2->getGeneration()); EXPECT_FALSE(vlanV2->isPublished()); EXPECT_EQ(VlanID(1234), vlanV2->getID()); EXPECT_EQ(kVlan1234, vlanV2->getName()); EXPECT_EQ(expectedPorts, vlanV2->getPorts()); EXPECT_EQ(InterfaceID(1), vlanV2->getInterfaceID()); // Check the ArpResponseTable auto arpRespTable = vlanV2->getArpResponseTable(); ArpResponseTable expectedArpResp; expectedArpResp.setEntry(IPAddressV4("10.1.1.1"), platformMac, InterfaceID(1)); EXPECT_EQ(expectedArpResp.getTable(), arpRespTable->getTable()); // Check the NdpResponseTable auto ndpRespTable = vlanV2->getNdpResponseTable(); NdpResponseTable expectedNdpResp; expectedNdpResp.setEntry(IPAddressV6("2a03:2880:10:1f07:face:b00c:0:0"), platformMac, InterfaceID(1)); // The link-local IPv6 address should also have been automatically added // to the NDP response table. expectedNdpResp.setEntry(IPAddressV6("fe80::8002:00ff:feab:cdef"), platformMac, InterfaceID(1)); EXPECT_EQ(expectedNdpResp.getTable(), ndpRespTable->getTable()); // Add another vlan and interface config.vlans.resize(2); config.vlans[1].id = 1299; config.vlans[1].name = kVlan1299; config.vlans[1].intfID = 2; config.interfaces.resize(2); config.interfaces[1].intfID = 2; config.interfaces[1].routerID = 0; config.interfaces[1].vlanID = 1299; config.interfaces[1].ipAddresses.resize(2); config.interfaces[1].ipAddresses[0] = "10.1.10.1/24"; config.interfaces[1].ipAddresses[1] = "192.168.0.1/31"; MacAddress intf2Mac("02:01:02:ab:cd:78"); config.interfaces[1].mac = intf2Mac.toString(); config.interfaces[1].__isset.mac = true; auto stateV3 = publishAndApplyConfig(stateV2, &config, &platform); auto vlanV3 = stateV3->getVlans()->getVlan(VlanID(1299)); EXPECT_EQ(0, vlanV3->getGeneration()); EXPECT_FALSE(vlanV3->isPublished()); EXPECT_EQ(VlanID(1299), vlanV3->getID()); EXPECT_EQ(kVlan1299, vlanV3->getName()); EXPECT_EQ(InterfaceID(2), vlanV3->getInterfaceID()); // Check the ArpResponseTable arpRespTable = vlanV3->getArpResponseTable(); ArpResponseTable expectedTable2; expectedTable2.setEntry(IPAddressV4("10.1.10.1"), intf2Mac, InterfaceID(2)); expectedTable2.setEntry(IPAddressV4("192.168.0.1"), intf2Mac, InterfaceID(2)); EXPECT_EQ(expectedTable2.getTable(), arpRespTable->getTable()); // The new interface has no IPv6 address, but the NDP table should still // be updated with the link-local address. NdpResponseTable expectedNdpResp2; expectedNdpResp2.setEntry(IPAddressV6("fe80::1:02ff:feab:cd78"), intf2Mac, InterfaceID(2)); EXPECT_EQ(expectedNdpResp2.getTable(), vlanV3->getNdpResponseTable()->getTable()); // Add a new VLAN with an ArpResponseTable that needs to be set up // when the VLAN is first created config.vlans.resize(3); config.vlans[2].id = 99; config.vlans[2].name = kVlan99; config.vlans[2].intfID = 3; config.interfaces.resize(3); config.interfaces[2].intfID = 3; config.interfaces[2].routerID = 1; config.interfaces[2].vlanID = 99; config.interfaces[2].ipAddresses.resize(2); config.interfaces[2].ipAddresses[0] = "1.2.3.4/24"; config.interfaces[2].ipAddresses[1] = "10.0.0.1/9"; auto stateV4 = publishAndApplyConfig(stateV3, &config, &platform); ASSERT_NE(nullptr, stateV4); // VLAN 1234 should be unchanged EXPECT_EQ(vlanV2, stateV4->getVlans()->getVlan(VlanID(1234))); auto vlan99 = stateV4->getVlans()->getVlan(VlanID(99)); auto vlan99_byName = stateV4->getVlans()->getVlanSlow(kVlan99); ASSERT_NE(nullptr, vlan99); EXPECT_EQ(vlan99, vlan99_byName); EXPECT_EQ(0, vlan99->getGeneration()); EXPECT_EQ(InterfaceID(3), vlan99->getInterfaceID()); ArpResponseTable expectedTable99; expectedTable99.setEntry(IPAddressV4("1.2.3.4"), platformMac, InterfaceID(3)); expectedTable99.setEntry(IPAddressV4("10.0.0.1"), platformMac, InterfaceID(3)); EXPECT_EQ(expectedTable99.getTable(), vlan99->getArpResponseTable()->getTable()); // Check vlan congfig with no intfID set config.vlans.resize(4); config.vlans[3].id = 100; config.vlans[3].__isset.intfID = false; config.interfaces.resize(4); config.interfaces[3].intfID = 4; config.interfaces[3].routerID = 0; config.interfaces[3].vlanID = 100; config.interfaces[3].ipAddresses.resize(2); config.interfaces[3].ipAddresses[0] = "10.50.3.7/24"; config.interfaces[3].ipAddresses[1] = "10.50.0.3/9"; auto stateV5 = publishAndApplyConfig(stateV4, &config, &platform); ASSERT_NE(nullptr, stateV5); auto vlan100 = stateV5->getVlans()->getVlan(VlanID(100)); EXPECT_EQ(InterfaceID(4), vlan100->getInterfaceID()); }
TEST(ArpTest, PendingArpCleanup) { std::chrono::seconds arpTimeout(1); std::chrono::seconds arpAgerInterval(1); auto sw = setupSwitch(arpTimeout, arpAgerInterval); VlanID vlanID(1); IPAddressV4 senderIP = IPAddressV4("10.0.0.1"); IPAddressV4 targetIP = IPAddressV4("10.0.0.2"); // Cache the current stats CounterCache counters(sw.get()); testSendArpRequest(sw, vlanID, senderIP, targetIP); waitForStateUpdates(sw.get()); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); // Should see a pending entry now auto entry = sw->getState()->getVlans()->getVlanIf(vlanID)->getArpTable() ->getEntryIf(targetIP); EXPECT_NE(entry, nullptr); EXPECT_EQ(entry->isPending(), true); // Send an Arp request for a different neighbor IPAddressV4 targetIP2 = IPAddressV4("10.0.0.3"); testSendArpRequest(sw, vlanID, senderIP, targetIP2); counters.update(); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.tx.sum", 1); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.request.rx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.tx.sum", 0); counters.checkDelta(SwitchStats::kCounterPrefix + "arp.reply.rx.sum", 0); // Should see another pending entry now waitForStateUpdates(sw.get()); entry = sw->getState()->getVlans()->getVlanIf(vlanID)->getArpTable() ->getEntryIf(targetIP2); EXPECT_NE(entry, nullptr); EXPECT_EQ(entry->isPending(), true); // Wait for pending entries to expire EXPECT_HW_CALL(sw, stateChanged(_)).Times(testing::AtLeast(1)); std::promise<bool> done; auto* evb = sw->getBackgroundEVB(); evb->runInEventBaseThread([&]() { evb->tryRunAfterDelay([&]() { done.set_value(true); }, 1050); }); done.get_future().wait(); // Entries should be removed entry = sw->getState()->getVlans()->getVlanIf(vlanID)->getArpTable() ->getEntryIf(targetIP); auto entry2 = sw->getState()->getVlans()->getVlanIf(vlanID)->getArpTable() ->getEntryIf(targetIP2); EXPECT_EQ(entry, nullptr); EXPECT_EQ(entry2, nullptr); }