Device Emulation (contd.): Receive, parse IPv6 Neigh Advt and update NDP Table

This commit is contained in:
Srivats P 2016-01-06 18:10:28 +05:30
parent d9be523827
commit eff603304e
6 changed files with 204 additions and 39 deletions

View File

@ -104,7 +104,7 @@ message ArpEntry {
optional uint64 mac = 2;
}
message NdEntry {
message NdpEntry {
optional Ip6Address ip6 = 1;
optional uint64 mac = 2;
}
@ -112,7 +112,7 @@ message NdEntry {
message DeviceNeighborList {
optional uint32 device_index = 1;
repeated ArpEntry arp = 2;
repeated NdEntry nd = 3;
repeated NdpEntry ndp = 3;
}
extend OstProto.PortNeighborList {

View File

@ -36,6 +36,7 @@ public:
quint8* toArray() const;
bool operator==(const UInt128 &other) const;
bool operator!=(const UInt128 &other) const;
UInt128 operator+(const UInt128 &other) const;
UInt128 operator*(const uint &other) const;
UInt128 operator<<(const int &shift) const;
@ -82,6 +83,11 @@ inline bool UInt128::operator==(const UInt128 &other) const
return ((hi_ == other.hi_) && (lo_ == other.lo_));
}
inline bool UInt128::operator!=(const UInt128 &other) const
{
return ((hi_ != other.hi_) || (lo_ != other.lo_));
}
inline UInt128 UInt128::operator+(const UInt128 &other) const
{
UInt128 sum;

View File

@ -235,6 +235,10 @@ void Device::receivePacket(PacketBuffer *pktBuf)
break;
case 0x86dd: // IPv6
if (hasIp6_)
receiveIp6(pktBuf);
break;
default:
break;
}
@ -283,16 +287,28 @@ void Device::resolveNeighbor(PacketBuffer *pktBuf)
// Append this device's neighbors to the list
void Device::getNeighbors(OstEmul::DeviceNeighborList *neighbors)
{
QList<quint32> ipList = arpTable_.keys();
QList<quint64> macList = arpTable_.values();
QList<quint32> ip4List = arpTable_.keys();
QList<UInt128> ip6List = ndpTable_.keys();
QList<quint64> macList;
Q_ASSERT(ipList.size() == macList.size());
macList = arpTable_.values();
Q_ASSERT(ip4List.size() == macList.size());
for (int i = 0; i < ipList.size(); i++) {
for (int i = 0; i < ip4List.size(); i++) {
OstEmul::ArpEntry *arp = neighbors->add_arp();
arp->set_ip4(ipList.at(i));
arp->set_ip4(ip4List.at(i));
arp->set_mac(macList.at(i));
}
macList = ndpTable_.values();
Q_ASSERT(ip6List.size() == macList.size());
for (int i = 0; i < ip6List.size(); i++) {
OstEmul::NdpEntry *ndp = neighbors->add_ndp();
ndp->mutable_ip6()->set_hi(ip6List.at(i).hi64());
ndp->mutable_ip6()->set_lo(ip6List.at(i).lo64());
ndp->set_mac(macList.at(i));
}
}
// Are we the source of the given packet?
@ -658,6 +674,46 @@ void Device::receiveIcmp4(PacketBuffer *pktBuf)
* ---------------------------------------------------------
*/
void Device::receiveIp6(PacketBuffer *pktBuf)
{
uchar *pktData = pktBuf->data();
uchar ipProto;
UInt128 dstIp;
if ((pktData[0] & 0xF0) != 0x60) {
qDebug("%s: Unsupported IP version (%02x) ", __FUNCTION__,
pktData[0]);
goto _invalid_exit;
}
if (pktBuf->length() < kIp6HdrLen) {
qDebug("incomplete IPv6 header: expected %d, actual %d",
kIp6HdrLen, pktBuf->length());
goto _invalid_exit;
}
dstIp = qFromBigEndian<UInt128>(pktData + 24);
if (dstIp != ip6_) {
qDebug("%s: dstIp %s is not me (%s)", __FUNCTION__,
qPrintable(QHostAddress(dstIp.toArray()).toString()),
qPrintable(QHostAddress(ip6_.toArray()).toString()));
goto _invalid_exit;
}
ipProto = pktData[6];
switch (ipProto) {
case kIpProtoIcmp6:
pktBuf->pull(kIp6HdrLen);
receiveIcmp6(pktBuf);
break;
default:
break;
}
_invalid_exit:
return;
}
// pktBuf should point to start of IP payload
bool Device::sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol)
{
@ -693,6 +749,81 @@ _error_exit:
return false;
}
void Device::receiveIcmp6(PacketBuffer *pktBuf)
{
uchar *pktData = pktBuf->data();
quint8 type = pktData[0];
// XXX: We don't verify icmp checksum
switch (type) {
case 135: // Neigh Solicit
case 136: // Neigh Advt
receiveNdp(pktBuf);
break;
default:
break;
}
}
void Device::receiveNdp(PacketBuffer *pktBuf)
{
uchar *pktData = pktBuf->data();
quint8 type = pktData[0];
if (pktBuf->length() < 32) {
qDebug("%s: incomplete NA header: expected 32, actual %d",
__FUNCTION__, pktBuf->length());
goto _invalid_exit;
}
switch (type)
{
case 135: { // Neigh Solicit
#if 0
quint32 sum;
pktData[0] = 0; // Echo Reply
// Incremental checksum update (RFC 1624 [Eqn.3])
// HC' = ~(~HC + ~m + m')
sum = quint16(~qFromBigEndian<quint16>(pktData + 2)); // old cksum
sum += quint16(~quint16(8 << 8 | pktData[1])); // old value
sum += quint16(0 << 8 | pktData[1]); // new value
while(sum >> 16)
sum = (sum & 0xFFFF) + (sum >> 16);
*(quint16*)(pktData + 2) = qToBigEndian(quint16(~sum));
sendIp4Reply(pktBuf);
qDebug("Sent ICMP Echo Reply");
#endif
break;
}
case 136: { // Neigh Advt
quint8 flags = pktData[4];
const quint8 kSFlag = 0x40;
const quint8 kOFlag = 0x20;
UInt128 tgtIp = qFromBigEndian<UInt128>(pktData + 8);
quint64 mac = ndpTable_.value(tgtIp);
// Update NDP table only for solicited responses
if (!(flags & kSFlag))
break;
if ((flags & kOFlag) || (mac == 0)) {
// Check if we have a Target Link-Layer TLV
if ((pktData[24] != 2) || (pktData[25] != 1))
goto _invalid_exit;
mac = qFromBigEndian<quint32>(pktData + 26);
mac = (mac << 16) | qFromBigEndian<quint16>(pktData + 30);
ndpTable_.insert(tgtIp, mac);
}
break;
}
}
_invalid_exit:
return;
}
// Send NS for the IPv6 packet in pktBuf
// caller is responsible to check that pktBuf originates from this device
// pktBuf should point to start of IP header

View File

@ -75,8 +75,12 @@ private: // methods
void receiveIcmp4(PacketBuffer *pktBuf);
void receiveIp6(PacketBuffer *pktBuf);
bool sendIp6(PacketBuffer *pktBuf, UInt128 dstIp, quint8 protocol);
void receiveIcmp6(PacketBuffer *pktBuf);
void receiveNdp(PacketBuffer *pktBuf);
void sendNeighborSolicit(PacketBuffer *pktBuf);
private: // data

View File

@ -966,18 +966,18 @@ void PcapPort::EmulationTransceiver::run()
libpcap changes their implementation, this will need to change as well.
*/
const char *capture_filter =
"arp or icmp or "
"(vlan and (arp or icmp)) or "
"(vlan and vlan and (arp or icmp)) or "
"(vlan and vlan and vlan and (arp or icmp)) or "
"(vlan and vlan and vlan and vlan and (arp or icmp))";
"arp or icmp or icmp6"
"(vlan and (arp or icmp or icmp6)) or "
"(vlan and vlan and (arp or icmp or icmp6)) or "
"(vlan and vlan and vlan and (arp or icmp or icmp6)) or "
"(vlan and vlan and vlan and vlan and (arp or icmp or icmp6))";
#else
const char *capture_filter =
"arp or icmp or "
"(vlan and (arp or icmp)) or "
"(vlan and (arp or icmp)) or "
"(vlan and (arp or icmp)) or "
"(vlan and (arp or icmp))";
"arp or icmp or icmp6 "
"(vlan and (arp or icmp or icmp6)) or "
"(vlan and (arp or icmp or icmp6)) or "
"(vlan and (arp or icmp or icmp6)) or "
"(vlan and (arp or icmp or icmp6))";
#endif
const int optimize = 1;

View File

@ -78,7 +78,10 @@ if not use_defaults:
# the python ipaddress module
class ip6_address(ipaddress.IPv6Interface):
def __init__(self, addr):
super(ip6_address, self).__init__(unicode(addr))
if type(addr) is str:
super(ip6_address, self).__init__(unicode(addr))
else:
super(ip6_address, self).__init__(addr.hi << 64 | addr.lo)
self.ip6 = emul.Ip6Address()
self.ip6.hi = int(self) >> 64
self.ip6.lo = int(self) & 0xffffffffffffffff
@ -544,6 +547,10 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip,
assert re.search('10.10.[1-2].1\d\d.*lladdr', arp_cache) == None
assert re.search('1234:[1-2]::\[\da-f]+.*lladdr', arp_cache) == None
# wait for interface to do DAD? Otherwise we don't get replies for NS
# FIXME: find alternative to sleep
time.sleep(5)
# resolve ARP on tx/rx ports
log.info('resolving Neighbors on tx/rx ports ...')
drone.startCapture(emul_ports)
@ -610,39 +617,56 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip,
print(cap_pkts)
assert cap_pkts.count('\n') == 0
# retrieve and verify ARP Table on tx/rx ports
log.info('retrieving ARP entries on tx port')
# retrieve and verify ARP/NDP Table on tx/rx ports
log.info('retrieving ARP/NDP entries on tx port')
device_list = drone.getDeviceList(emul_ports.port_id[0])
device_config = device_list.Extensions[emul.port_device]
neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[0])
devices = neigh_list.Extensions[emul.devices]
log.info('ARP Table on tx port')
log.info('ARP/NDP Table on tx port')
for dev_cfg, device in zip(device_config, devices):
resolved = False
for arp in device.arp:
print('%s: %s %012x' %
(str(ipaddress.ip_address(dev_cfg.ip4)),
str(ipaddress.ip_address(arp.ip4)),
arp.mac))
if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac):
resolved = True
# TODO: ip6/ndp
assert resolved
if has_ip4:
for arp in device.arp:
print('%s: %s %012x' %
(str(ipaddress.ip_address(dev_cfg.ip4)),
str(ipaddress.ip_address(arp.ip4)),
arp.mac))
if (arp.ip4 == dev_cfg.ip4_default_gateway) and (arp.mac):
resolved = True
assert resolved
if has_ip6:
for ndp in device.ndp:
print('%s: %s %012x' %
(str(ip6_address(dev_cfg.ip6)),
str(ip6_address(ndp.ip6)),
ndp.mac))
if (ndp.ip6 == dev_cfg.ip6_default_gateway) and (ndp.mac):
resolved = True
assert resolved
log.info('retrieving ARP entries on rx port')
log.info('retrieving ARP/NDP entries on rx port')
device_list = drone.getDeviceList(emul_ports.port_id[0])
device_config = device_list.Extensions[emul.port_device]
neigh_list = drone.getDeviceNeighbors(emul_ports.port_id[1])
devices = neigh_list.Extensions[emul.devices]
log.info('ARP Table on rx port')
log.info('ARP/NDP Table on rx port')
for dev_cfg, device in zip(device_config, devices):
# verify *no* ARPs learnt on rx port
assert len(device.arp) == 0
for arp in device.arp:
# TODO: pretty print ip and mac
print('%08x: %08x %012x' %
(dev_cfg.ip4, arp.ip4, arp.mac))
# TODO: ip6/ndp
# verify *no* ARPs/NDPs learnt on rx port
if has_ip4:
assert len(device.arp) == 0
for arp in device.arp:
print('%s: %s %012x' %
(str(ipaddress.ip_address(dev_cfg.ip4)),
str(ipaddress.ip_address(arp.ip4)),
arp.mac))
if has_ip6:
assert len(device.ndp) == 0
for ndp in device.ndp:
print('%s: %s %012x' %
(str(ip6_address(dev_cfg.ip6)),
str(ip6_address(ndp.ip6)),
ndp.mac))
# ping the tx devices from the DUT
for i in range(num_devs):