From 04147076c4cdc3c1954eb0db8cf8c11b3472968e Mon Sep 17 00:00:00 2001 From: Srivats P Date: Fri, 8 Jan 2016 20:01:42 +0530 Subject: [PATCH] Device Emulation (contd.): Implemented responding IPv6 NS with NA --- server/device.cpp | 106 +++++++++++++++++++++++++++++++-------- server/device.h | 1 + server/devicemanager.cpp | 17 +++++-- test/emultest.py | 22 +++++--- 4 files changed, 116 insertions(+), 30 deletions(-) diff --git a/server/device.cpp b/server/device.cpp index 1323a33..572c3da 100644 --- a/server/device.cpp +++ b/server/device.cpp @@ -52,6 +52,11 @@ inline quint32 sumUInt128(UInt128 value) return sum; } +inline bool isIp6Mcast(UInt128 ip) +{ + return (ip.hi64() >> 56) == 0xff; +} + Device::Device(DeviceManager *deviceManager) { deviceManager_ = deviceManager; @@ -588,12 +593,14 @@ void Device::receiveIp4(PacketBuffer *pktBuf) } ipProto = pktData[9]; + qDebug("%s: ipProto = %d", __FUNCTION__, ipProto); switch (ipProto) { case 1: // ICMP pktBuf->pull(20); receiveIcmp4(pktBuf); break; default: + qWarning("%s: Unsupported ipProto %d", __FUNCTION__, ipProto); break; } @@ -692,8 +699,9 @@ void Device::receiveIp6(PacketBuffer *pktBuf) goto _invalid_exit; } + // FIXME: check for specific mcast address(es) instead of any mcast? dstIp = qFromBigEndian(pktData + 24); - if (dstIp != ip6_) { + if (!isIp6Mcast(dstIp) && (dstIp != ip6_)) { qDebug("%s: dstIp %s is not me (%s)", __FUNCTION__, qPrintable(QHostAddress(dstIp.toArray()).toString()), qPrintable(QHostAddress(ip6_.toArray()).toString())); @@ -770,32 +778,20 @@ void Device::receiveNdp(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->data(); quint8 type = pktData[0]; + int len = pktBuf->length(); + int minLen = 24 + (type == 136 ? 8 : 0); // NA should have the Target TLV - if (pktBuf->length() < 32) { - qDebug("%s: incomplete NA header: expected 32, actual %d", - __FUNCTION__, pktBuf->length()); + if (len < minLen) { + qDebug("%s: incomplete NS/NA header: expected %d, actual %d", + __FUNCTION__, minLen, 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(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 + // TODO: Validation as per RFC 4861 + sendNeighborAdvertisement(pktBuf); break; } case 136: { // Neigh Advt @@ -902,6 +898,76 @@ void Device::sendNeighborSolicit(PacketBuffer *pktBuf) qPrintable(QHostAddress(tgtIp.toArray()).toString())); } +// Send NA for the NS packet in pktBuf +// pktBuf should point to start of ICMPv6 header +void Device::sendNeighborAdvertisement(PacketBuffer *pktBuf) +{ + PacketBuffer *naPkt; + uchar *pktData = pktBuf->data(); + quint16 flags = 0x6000; // solicit = 1; overide = 1 + uchar *ip6Hdr; + UInt128 tgtIp, srcIp; + + tgtIp = qFromBigEndian(pktData + 8); + if (tgtIp != ip6_) { + qDebug("%s: NS tgtIp %s is not us %s", __FUNCTION__, + qPrintable(QHostAddress(tgtIp.toArray()).toString()), + qPrintable(QHostAddress(ip6_.toArray()).toString())); + ip6Hdr = pktBuf->push(kIp6HdrLen); + return; + } + + ip6Hdr = pktBuf->push(kIp6HdrLen); + srcIp = qFromBigEndian(ip6Hdr + 8); + + if (srcIp == UInt128(0, 0)) { + // reset solicit flag + flags &= ~0x4000; + // NA should be sent to All nodes address + srcIp = UInt128(quint64(0xff02) << 48, quint64(1)); + } + + naPkt = new PacketBuffer; + naPkt->reserve(encapSize() + kIp6HdrLen); + pktData = naPkt->put(32); + if (pktData) { + // Calculate checksum first - + // start with fixed fields in ICMP Header and IPv6 Pseudo Header ... + quint32 sum = (0x8800 + flags + 0x0201) + (32 + kIpProtoIcmp6); + + // then variable fields from ICMP header ... + sum += sumUInt128(tgtIp); + sum += (mac_ >> 32) + ((mac_ >> 16) & 0xffff) + (mac_ & 0xffff); + + // and variable fields from IPv6 pseudo header + sum += sumUInt128(ip6_); + sum += sumUInt128(srcIp); + + while(sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + + // Type, Code + *(quint16*)(pktData ) = qToBigEndian(quint16(0x8800)); + // Checksum + *(quint16*)(pktData+ 2) = qToBigEndian(quint16(~sum)); + // Flags-Reserved + *(quint32*)(pktData+ 4) = qToBigEndian(quint32(flags << 16)); + // Target IP + memcpy(pktData+ 8, tgtIp.toArray(), 16); + // Target Addr TLV + MacAddr + *(quint16*)(pktData+24) = qToBigEndian(quint16(0x0201)); + *(quint32*)(pktData+26) = qToBigEndian(quint32(mac_ >> 16)); + *(quint16*)(pktData+30) = qToBigEndian(quint16(mac_ & 0xffff)); + } + + if (!sendIp6(naPkt, srcIp , kIpProtoIcmp6)) + return; + + qDebug("Sent Neigh Advt to dstIp for tgtIp=%s/%s", + qPrintable(QHostAddress(srcIp.toArray()).toString()), + qPrintable(QHostAddress(tgtIp.toArray()).toString())); +} + bool operator<(const DeviceKey &a1, const DeviceKey &a2) { int i = 0; diff --git a/server/device.h b/server/device.h index d7738d8..e747698 100644 --- a/server/device.h +++ b/server/device.h @@ -82,6 +82,7 @@ private: // methods void receiveNdp(PacketBuffer *pktBuf); void sendNeighborSolicit(PacketBuffer *pktBuf); + void sendNeighborAdvertisement(PacketBuffer *pktBuf); private: // data static const int kMaxVlan = 4; diff --git a/server/devicemanager.cpp b/server/devicemanager.cpp index d3393f7..d82714b 100644 --- a/server/devicemanager.cpp +++ b/server/devicemanager.cpp @@ -31,12 +31,18 @@ along with this program. If not, see #define __STDC_FORMAT_MACROS #include +const quint64 kBcastMac = 0xffffffffffffULL; + inline UInt128 UINT128(OstEmul::Ip6Address x) { return UInt128(x.hi(), x.lo()); } -const quint64 kBcastMac = 0xffffffffffffULL; +inline bool isMacMcast(quint64 mac) +{ + return (mac >> 40) & 0x01 == 0x01; +} + // XXX: Port owning DeviceManager already uses locks, so we don't use any // locks within DeviceManager to protect deviceGroupList_ et.al. @@ -195,14 +201,19 @@ void DeviceManager::receivePacket(PacketBuffer *pktBuf) dstMac = qFromBigEndian(pktData + offset); offset += 4; dstMac = (dstMac << 16) | qFromBigEndian(pktData + offset); + + qDebug("dstMac %012" PRIx64, dstMac); + + // XXX: Treat multicast as bcast + if (isMacMcast(dstMac)) + dstMac = kBcastMac; + dk.setMac(dstMac); offset += 2; // Skip srcMac - don't care offset += 6; - qDebug("dstMac %012" PRIx64, dstMac); - _eth_type: // Extract EthType ethType = qFromBigEndian(pktData + offset); diff --git a/test/emultest.py b/test/emultest.py index a0da466..09a669c 100644 --- a/test/emultest.py +++ b/test/emultest.py @@ -670,15 +670,23 @@ def test_multiEmulDevNoVlan(drone, ports, dut, dut_ports, dut_ip, # ping the tx devices from the DUT for i in range(num_devs): - out = run('ping -c3 10.10.1.'+str(101+i*ip_step), warn_only=True) - assert '100% packet loss' not in out - # TODO: ip6/ndp + if has_ip4: + out = run('ping -c3 10.10.1.'+str(101+i*ip_step), warn_only=True) + assert '100% packet loss' not in out + if has_ip6: + out = run('ping -6 -c3 1234:1::'+format(101+i*ip_step, 'x'), + warn_only=True) + assert '100% packet loss' not in out - # ping the tx devices from the DUT + # ping the rx devices from the DUT for i in range(num_devs): - out = run('ping -c3 10.10.2.'+str(101+i*ip_step), warn_only=True) - assert '100% packet loss' not in out - # TODO: ip6/ndp + if has_ip4: + out = run('ping -c3 10.10.2.'+str(101+i*ip_step), warn_only=True) + assert '100% packet loss' not in out + if has_ip6: + out = run('ping -6 -c3 1234:2::'+format(101+i*ip_step, 'x'), + warn_only=True) + assert '100% packet loss' not in out # We are all set now - so transmit the stream now drone.startCapture(ports.rx)