/* Copyright (C) 2015 Srivats P. This file is part of "Ostinato" This is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see */ #include "device.h" #include "../common/emulproto.pb.h" #include "devicemanager.h" #include "packetbuffer.h" #include #include const int kBaseHex = 16; const quint64 kBcastMac = 0xffffffffffffULL; const quint16 kEthTypeIp4 = 0x0800; const quint16 kEthTypeIp6 = 0x86dd; const int kIp6HdrLen = 40; const quint8 kIpProtoIcmp6 = 58; /* * NOTE: * 1. Device Key is (VLANS + MAC) - is assumed to be unique for a device * 2. Device clients/users (viz. DeviceManager) should take care when * setting params that change the key, if the key is used elsewhere * (e.g. in a hash) */ inline quint32 sumUInt128(UInt128 value) { quint8 *arr = value.toArray(); quint32 sum = 0; for (int i = 0; i < 16; i += 2) sum += qToBigEndian(*((quint16*)(arr + i))); return sum; } inline bool isIp6Mcast(UInt128 ip) { return (ip.hi64() >> 56) == 0xff; } Device::Device(DeviceManager *deviceManager) { deviceManager_ = deviceManager; for (int i = 0; i < kMaxVlan; i++) vlan_[i] = 0; numVlanTags_ = 0; mac_ = 0; hasIp4_ = false; hasIp6_ = false; clearKey(); } void Device::setVlan(int index, quint16 vlan, quint16 tpid) { int ofs; if ((index < 0) || (index >= kMaxVlan)) { qWarning("%s: vlan index %d out of range (0 - %d)", __FUNCTION__, index, kMaxVlan - 1); return; } vlan_[index] = (tpid << 16) | vlan; ofs = index * sizeof(quint16); key_[ofs] = vlan >> 8; key_[ofs+1] = vlan & 0xff; if (index >= numVlanTags_) numVlanTags_ = index + 1; } quint64 Device::mac() { return mac_; } void Device::setMac(quint64 mac) { int ofs = kMaxVlan * sizeof(quint16); mac_ = mac & ~(0xffffULL << 48); memcpy(key_.data() + ofs, (char*)&mac_, sizeof(mac_)); } void Device::setIp4(quint32 address, int prefixLength, quint32 gateway) { ip4_ = address; ip4PrefixLength_ = prefixLength; ip4Gateway_ = gateway; hasIp4_ = true; } void Device::setIp6(UInt128 address, int prefixLength, UInt128 gateway) { ip6_ = address; ip6PrefixLength_ = prefixLength; ip6Gateway_ = gateway; hasIp6_ = true; } void Device::getConfig(OstEmul::Device *deviceConfig) { for (int i = 0; i < numVlanTags_; i++) deviceConfig->add_vlan(vlan_[i]); deviceConfig->set_mac(mac_); if (hasIp4_) { deviceConfig->set_ip4(ip4_); deviceConfig->set_ip4_prefix_length(ip4PrefixLength_); deviceConfig->set_ip4_default_gateway(ip4Gateway_); } if (hasIp6_) { deviceConfig->mutable_ip6()->set_hi(ip6_.hi64()); deviceConfig->mutable_ip6()->set_lo(ip6_.lo64()); deviceConfig->set_ip6_prefix_length(ip6PrefixLength_); deviceConfig->mutable_ip6_default_gateway()->set_hi(ip6Gateway_.hi64()); deviceConfig->mutable_ip6_default_gateway()->set_lo(ip6Gateway_.lo64()); } } QString Device::config() { QString config; for (int i = 0; i < numVlanTags_; i++) { config.append(i == 0 ? "vlans=" : "|"); config.append( (vlan_[i] >> 16) != kVlanTpid ? QString("0x%1-%2") .arg(vlan_[i] >> 16, 4, kBaseHex, QChar('0')) .arg(vlan_[i] & 0xFFFF) : QString("%1") .arg(vlan_[i] & 0xFFFF)); } config.append(QString(" mac=%1") .arg(mac_, 12, kBaseHex, QChar('0'))); if (hasIp4_) config.append(QString(" ip4=%1/%2") .arg(QHostAddress(ip4_).toString()) .arg(ip4PrefixLength_)); if (hasIp6_) config.append(QString(" ip6=%1/%2") .arg(QHostAddress(ip6_.toArray()).toString()) .arg(ip6PrefixLength_)); return config; } DeviceKey Device::key() { return key_; } void Device::clearKey() { key_.fill(0, kMaxVlan * sizeof(quint16) + sizeof(quint64)); } int Device::encapSize() { // ethernet header + vlans int size = 14 + 4*numVlanTags_; return size; } void Device::encap(PacketBuffer *pktBuf, quint64 dstMac, quint16 type) { int ofs; quint64 srcMac = mac_; uchar *p = pktBuf->push(encapSize()); if (!p) { qWarning("%s: failed to push %d bytes [0x%p, 0x%p]", __FUNCTION__, encapSize(), pktBuf->head(), pktBuf->data()); goto _exit; } *(quint32*)(p ) = qToBigEndian(quint32(dstMac >> 16)); *(quint16*)(p + 4) = qToBigEndian(quint16(dstMac & 0xffff)); *(quint32*)(p + 6) = qToBigEndian(quint32(srcMac >> 16)); *(quint16*)(p + 10) = qToBigEndian(quint16(srcMac & 0xffff)); ofs = 12; for (int i = 0; i < numVlanTags_; i++) { *(quint32*)(p + ofs) = qToBigEndian(vlan_[i]); ofs += 4; } *(quint16*)(p + ofs) = qToBigEndian(type); ofs += 2; Q_ASSERT(ofs == encapSize()); _exit: return; } // We expect pktBuf to point to EthType on entry void Device::receivePacket(PacketBuffer *pktBuf) { quint16 ethType = qFromBigEndian(pktBuf->data()); pktBuf->pull(2); qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); switch(ethType) { case 0x0806: // ARP if (hasIp4_) receiveArp(pktBuf); break; case 0x0800: // IPv4 if (hasIp4_) receiveIp4(pktBuf); break; case 0x86dd: // IPv6 if (hasIp6_) receiveIp6(pktBuf); break; default: break; } // FIXME: temporary hack till DeviceManager clones pbufs pktBuf->push(2); } void Device::transmitPacket(PacketBuffer *pktBuf) { deviceManager_->transmitPacket(pktBuf); } void Device::resolveGateway() { if (hasIp4_) sendArpRequest(ip4Gateway_); if (hasIp6_) sendNeighborSolicit(ip6Gateway_); } void Device::clearNeighbors(Device::NeighborSet set) { QMutableHashIterator arpIter(arpTable_); QMutableHashIterator ndpIter(ndpTable_); switch (set) { case kAllNeighbors: arpTable_.clear(); ndpTable_.clear(); break; case kUnresolvedNeighbors: while (arpIter.hasNext()) { arpIter.next(); if (arpIter.value() == 0) arpIter.remove(); } while (ndpIter.hasNext()) { ndpIter.next(); if (ndpIter.value() == 0) ndpIter.remove(); } break; default: Q_ASSERT(false); // Unreachable! } } // Resolve the Neighbor IP address for this to-be-transmitted pktBuf // We expect pktBuf to point to EthType on entry void Device::resolveNeighbor(PacketBuffer *pktBuf) { quint16 ethType = qFromBigEndian(pktBuf->data()); pktBuf->pull(2); qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); switch(ethType) { case 0x0800: // IPv4 if (hasIp4_) sendArpRequest(pktBuf); break; case 0x86dd: // IPv6 if (hasIp6_) sendNeighborSolicit(pktBuf); break; default: break; } // FIXME: temporary hack till DeviceManager clones pbufs pktBuf->push(2); } // Append this device's neighbors to the list void Device::getNeighbors(OstEmul::DeviceNeighborList *neighbors) { QList ip4List = arpTable_.keys(); QList ip6List = ndpTable_.keys(); QList macList; macList = arpTable_.values(); Q_ASSERT(ip4List.size() == macList.size()); for (int i = 0; i < ip4List.size(); i++) { OstEmul::ArpEntry *arp = neighbors->add_arp(); 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? // We expect pktBuf to point to EthType on entry bool Device::isOrigin(const PacketBuffer *pktBuf) { const uchar *pktData = pktBuf->data(); quint16 ethType = qFromBigEndian(pktData); qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); pktData += 2; // We know only about IP packets - adjust for ethType length (2 bytes) // when checking that we have a complete IP header if ((ethType == 0x0800) && hasIp4_) { // IPv4 int ipHdrLen = (pktData[0] & 0x0F) << 2; quint32 srcIp; if (pktBuf->length() < (ipHdrLen+2)) { qDebug("incomplete IPv4 header: expected %d, actual %d", ipHdrLen, pktBuf->length()); return false; } srcIp = qFromBigEndian(pktData + ipHdrLen - 8); qDebug("%s: pktSrcIp/selfIp = 0x%x/0x%x", __FUNCTION__, srcIp, ip4_); return (srcIp == ip4_); } else if ((ethType == kEthTypeIp6) && hasIp6_) { // IPv6 UInt128 srcIp; if (pktBuf->length() < (kIp6HdrLen+2)) { qDebug("incomplete IPv6 header: expected %d, actual %d", kIp6HdrLen, pktBuf->length()-2); return false; } srcIp = qFromBigEndian(pktData + 8); qDebug("%s: pktSrcIp6/selfIp6 = %llx-%llx/%llx-%llx", __FUNCTION__, srcIp.hi64(), srcIp.lo64(), ip6_.hi64(), ip6_.lo64()); return (srcIp == ip6_); } return false; } // Return the mac address corresponding to the dstIp of the given packet // We expect pktBuf to point to EthType on entry quint64 Device::neighborMac(const PacketBuffer *pktBuf) { const uchar *pktData = pktBuf->data(); quint16 ethType = qFromBigEndian(pktData); qDebug("%s: ethType 0x%x", __PRETTY_FUNCTION__, ethType); pktData += 2; // We know only about IP packets if ((ethType == 0x0800) && hasIp4_) { // IPv4 int ipHdrLen = (pktData[0] & 0x0F) << 2; quint32 dstIp, tgtIp, mask; if (pktBuf->length() < ipHdrLen) { qDebug("incomplete IPv4 header: expected %d, actual %d", ipHdrLen, pktBuf->length()); return false; } dstIp = qFromBigEndian(pktData + ipHdrLen - 4); if ((dstIp & 0xF0000000) == 0xE0000000) { // Mcast IP? qDebug("mcast dst %x", dstIp); return (quint64(0x01005e) << 24) | (dstIp & 0x7FFFFF); } mask = ~0 << (32 - ip4PrefixLength_); qDebug("dst %x mask %x self %x", dstIp, mask, ip4_); tgtIp = ((dstIp & mask) == (ip4_ & mask)) ? dstIp : ip4Gateway_; return arpTable_.value(tgtIp); } else if ((ethType == kEthTypeIp6) && hasIp6_) { // IPv6 UInt128 dstIp, tgtIp, mask; if (pktBuf->length() < (kIp6HdrLen+2)) { qDebug("incomplete IPv6 header: expected %d, actual %d", kIp6HdrLen, pktBuf->length()-2); return false; } dstIp = qFromBigEndian(pktData + 24); if (dstIp.toArray()[0] == 0xFF) { // Mcast IP? qDebug("mcast dst %s", qPrintable(QHostAddress(dstIp.toArray()).toString())); return (quint64(0x3333) << 32) | (dstIp.lo64() & 0xFFFFFFFF); } mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); qDebug("dst %s mask %s self %s", qPrintable(QHostAddress(dstIp.toArray()).toString()), qPrintable(QHostAddress(mask.toArray()).toString()), qPrintable(QHostAddress(ip6_.toArray()).toString())); tgtIp = ((dstIp & mask) == (ip6_ & mask)) ? dstIp : ip6Gateway_; return ndpTable_.value(tgtIp); } return false; } // // Private Methods // /* * --------------------------------------------------------- * IPv4 related private methods * --------------------------------------------------------- */ void Device::receiveArp(PacketBuffer *pktBuf) { PacketBuffer *rspPkt; uchar *pktData = pktBuf->data(); int offset = 0; quint16 hwType, protoType; quint8 hwAddrLen, protoAddrLen; quint16 opCode; quint64 srcMac, tgtMac; quint32 srcIp, tgtIp; // Extract tgtIp first to check quickly if this packet is for us or not tgtIp = qFromBigEndian(pktData + 24); if (tgtIp != ip4_) { qDebug("tgtIp %s is not me %s", qPrintable(QHostAddress(tgtIp).toString()), qPrintable(QHostAddress(ip4_).toString())); return; } // Extract annd verify ARP packet contents hwType = qFromBigEndian(pktData + offset); offset += 2; if (hwType != 1) // Mac goto _invalid_exit; protoType = qFromBigEndian(pktData + offset); offset += 2; if (protoType != 0x0800) // IPv4 goto _invalid_exit; hwAddrLen = pktData[offset]; offset += 1; if (hwAddrLen != 6) goto _invalid_exit; protoAddrLen = pktData[offset]; offset += 1; if (protoAddrLen != 4) goto _invalid_exit; opCode = qFromBigEndian(pktData + offset); offset += 2; srcMac = qFromBigEndian(pktData + offset); offset += 4; srcMac = (srcMac << 16) | qFromBigEndian(pktData + offset); offset += 2; srcIp = qFromBigEndian(pktData + offset); offset += 4; tgtMac = qFromBigEndian(pktData + offset); offset += 4; tgtMac = (tgtMac << 16) | qFromBigEndian(pktData + offset); offset += 2; switch (opCode) { case 1: // ARP Request arpTable_.insert(srcIp, srcMac); rspPkt = new PacketBuffer; rspPkt->reserve(encapSize()); pktData = rspPkt->put(28); if (pktData) { // HTYP, PTYP *(quint32*)(pktData ) = qToBigEndian(quint32(0x00010800)); // HLEN, PLEN, OPER *(quint32*)(pktData+ 4) = qToBigEndian(quint32(0x06040002)); // Source H/W Addr, Proto Addr *(quint32*)(pktData+ 8) = qToBigEndian(quint32(mac_ >> 16)); *(quint16*)(pktData+12) = qToBigEndian(quint16(mac_ & 0xffff)); *(quint32*)(pktData+14) = qToBigEndian(ip4_); // Target H/W Addr, Proto Addr *(quint32*)(pktData+18) = qToBigEndian(quint32(srcMac >> 16)); *(quint16*)(pktData+22) = qToBigEndian(quint16(srcMac & 0xffff)); *(quint32*)(pktData+24) = qToBigEndian(srcIp); } encap(rspPkt, srcMac, 0x0806); transmitPacket(rspPkt); qDebug("Sent ARP Reply for srcIp/tgtIp=%s/%s", qPrintable(QHostAddress(srcIp).toString()), qPrintable(QHostAddress(tgtIp).toString())); break; case 2: // ARP Response arpTable_.insert(srcIp, srcMac); break; default: break; } return; _invalid_exit: qWarning("Invalid ARP content"); return; } // Send ARP request for the IPv4 packet in pktBuf // pktBuf points to start of IP header void Device::sendArpRequest(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->data(); int ipHdrLen = (pktData[0] & 0x0F) << 2; quint32 srcIp = ip4_, dstIp, mask, tgtIp; if (pktBuf->length() < ipHdrLen) { qDebug("incomplete IPv4 header: expected %d, actual %d", ipHdrLen, pktBuf->length()); return; } dstIp = qFromBigEndian(pktData + ipHdrLen - 4); mask = ~0 << (32 - ip4PrefixLength_); qDebug("dst %x src %x mask %x", dstIp, srcIp, mask); tgtIp = ((dstIp & mask) == (srcIp & mask)) ? dstIp : ip4Gateway_; sendArpRequest(tgtIp); } void Device::sendArpRequest(quint32 tgtIp) { quint32 srcIp = ip4_; PacketBuffer *reqPkt; uchar *pktData; // Validate target IP if (!tgtIp) return; // This function will be called once per unique stream - which // may all have the same dst IP; even if dst IP are different the // gateway for the different dst IP may all be same. However, // we don't want to send duplicate ARP requests, so we check // if the tgtIP is already in the cache (resolved or unresolved) // and if so, we don't resend it if (arpTable_.contains(tgtIp)) return; reqPkt = new PacketBuffer; reqPkt->reserve(encapSize()); pktData = reqPkt->put(28); if (pktData) { // HTYP, PTYP *(quint32*)(pktData ) = qToBigEndian(quint32(0x00010800)); // HLEN, PLEN, OPER *(quint32*)(pktData+ 4) = qToBigEndian(quint32(0x06040001)); // Source H/W Addr, Proto Addr *(quint32*)(pktData+ 8) = qToBigEndian(quint32(mac_ >> 16)); *(quint16*)(pktData+12) = qToBigEndian(quint16(mac_ & 0xffff)); *(quint32*)(pktData+14) = qToBigEndian(srcIp); // Target H/W Addr, Proto Addr *(quint32*)(pktData+18) = qToBigEndian(quint32(0)); *(quint16*)(pktData+22) = qToBigEndian(quint16(0)); *(quint32*)(pktData+24) = qToBigEndian(tgtIp); } encap(reqPkt, kBcastMac, 0x0806); transmitPacket(reqPkt); arpTable_.insert(tgtIp, 0); qDebug("Sent ARP Request for srcIp/tgtIp=%s/%s", qPrintable(QHostAddress(srcIp).toString()), qPrintable(QHostAddress(tgtIp).toString())); } void Device::receiveIp4(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->data(); uchar ipProto; quint32 dstIp; if (pktData[0] != 0x45) { qDebug("%s: Unsupported IP version or options (%02x) ", __FUNCTION__, pktData[0]); goto _invalid_exit; } if (pktBuf->length() < 20) { qDebug("incomplete IPv4 header: expected 20, actual %d", pktBuf->length()); goto _invalid_exit; } // XXX: We don't verify IP Header checksum dstIp = qFromBigEndian(pktData + 16); if (dstIp != ip4_) { qDebug("%s: dstIp %x is not me (%x)", __FUNCTION__, dstIp, ip4_); goto _invalid_exit; } 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; } _invalid_exit: return; } // This function assumes we are replying back to the same IP // that originally sent us the packet and therefore we can reuse the // ingress packet for egress; in other words, it assumes the // original IP header is intact and will just reuse it after // minimal modifications void Device::sendIp4Reply(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->push(20); uchar origTtl = pktData[8]; uchar ipProto = pktData[9]; quint32 srcIp, dstIp, tgtIp, mask; quint32 sum; // Swap src/dst IP addresses dstIp = qFromBigEndian(pktData + 12); // srcIp in original pkt srcIp = qFromBigEndian(pktData + 16); // dstIp in original pkt mask = ~0 << (32 - ip4PrefixLength_); qDebug("dst %x mask %x self %x", dstIp, mask, ip4_); tgtIp = ((dstIp & mask) == (ip4_ & mask)) ? dstIp : ip4Gateway_; if (!arpTable_.contains(tgtIp)) { qWarning("%s: mac not found for %s; unable to send IPv4 packet", __FUNCTION__, qPrintable(QHostAddress(tgtIp).toString())); return; } *(quint32*)(pktData + 12) = qToBigEndian(srcIp); *(quint32*)(pktData + 16) = qToBigEndian(dstIp); // Reset TTL pktData[8] = 64; // Incremental checksum update (RFC 1624 [Eqn.3]) // HC' = ~(~HC + ~m + m') sum = quint16(~qFromBigEndian(pktData + 10)); // old cksum sum += quint16(~quint16(origTtl << 8 | ipProto)); // old value sum += quint16(pktData[8] << 8 | ipProto); // new value while(sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); *(quint16*)(pktData + 10) = qToBigEndian(quint16(~sum)); encap(pktBuf, arpTable_.value(tgtIp), 0x0800); transmitPacket(pktBuf); } void Device::receiveIcmp4(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->data(); quint32 sum; // XXX: We don't verify icmp checksum // We handle only ping request if (pktData[0] != 8) { // Echo Request qDebug("%s: Ignoring non echo request (%d)", __FUNCTION__, pktData[0]); return; } 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"); } /* * --------------------------------------------------------- * IPV6 related private methods * --------------------------------------------------------- */ 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; } // FIXME: check for specific mcast address(es) instead of any mcast? dstIp = qFromBigEndian(pktData + 24); if (!isIp6Mcast(dstIp) && (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) { int payloadLen = pktBuf->length(); uchar *p = pktBuf->push(kIp6HdrLen); quint64 dstMac; if (!p) { qWarning("%s: failed to push %d bytes [0x%p, 0x%p]", __FUNCTION__, kIp6HdrLen, pktBuf->head(), pktBuf->data()); goto _error_exit; } // In case of mcast, derive dstMac if ((dstIp.hi64() >> 56) == 0xff) dstMac = (quint64(0x3333) << 32) | (dstIp.lo64() & 0xffffffff); else { UInt128 mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); UInt128 tgtIp = ((dstIp & mask) == (ip6_ & mask)) ? dstIp : ip6Gateway_; qDebug("dst %s mask %s self %s", qPrintable(QHostAddress(dstIp.toArray()).toString()), qPrintable(QHostAddress(mask.toArray()).toString()), qPrintable(QHostAddress(ip6_.toArray()).toString())); dstMac = ndpTable_.value(tgtIp); } if (!dstMac) { qWarning("%s: mac not found for %s; unable to send IPv6 packet", __FUNCTION__, qPrintable(QHostAddress(dstIp.toArray()).toString())); goto _error_exit; } // Ver(4), TrfClass(8), FlowLabel(8) *(quint32*)(p ) = qToBigEndian(quint32(0x60000000)); *(quint16*)(p+ 4) = qToBigEndian(quint16(payloadLen)); p[6] = protocol; p[7] = 255; // HopLimit memcpy(p+ 8, ip6_.toArray(), 16); // Source IP memcpy(p+24, dstIp.toArray(), 16); // Destination IP // FIXME: both these functions should return success/failure encap(pktBuf, dstMac, kEthTypeIp6); transmitPacket(pktBuf); return true; _error_exit: return false; } // This function assumes we are replying back to the same IP // that originally sent us the packet and therefore we can reuse the // ingress packet for egress; in other words, it assumes the // original IP header is intact and will just reuse it after // minimal modifications void Device::sendIp6Reply(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->push(kIp6HdrLen); UInt128 srcIp, dstIp, tgtIp, mask; // Swap src/dst IP addresses dstIp = qFromBigEndian(pktData + 8); // srcIp in original pkt srcIp = qFromBigEndian(pktData + 24); // dstIp in original pkt mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); qDebug("dst %s mask %s self %s", qPrintable(QHostAddress(dstIp.toArray()).toString()), qPrintable(QHostAddress(mask.toArray()).toString()), qPrintable(QHostAddress(ip6_.toArray()).toString())); tgtIp = ((dstIp & mask) == (ip6_ & mask)) ? dstIp : ip6Gateway_; if (!ndpTable_.contains(tgtIp)) { qWarning("%s: mac not found for %s; unable to send IPv6 packet", __FUNCTION__, qPrintable(QHostAddress(tgtIp.toArray()).toString())); return; } memcpy(pktData + 8, srcIp.toArray(), 16); // Source IP memcpy(pktData + 24, dstIp.toArray(), 16); // Destination IP // Reset TTL pktData[7] = 64; encap(pktBuf, ndpTable_.value(tgtIp), 0x86dd); transmitPacket(pktBuf); } void Device::receiveIcmp6(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->data(); quint8 type = pktData[0]; quint32 sum; // XXX: We don't verify icmp checksum switch (type) { case 128: // ICMPv6 Echo Request pktData[0] = 129; // Echo Reply // Incremental checksum update (RFC 1624 [Eqn.3]) // HC' = ~(~HC + ~m + m') sum = quint16(~qFromBigEndian(pktData + 2)); // old cksum sum += quint16(~quint16(128 << 8 | pktData[1])); // old value sum += quint16(129 << 8 | pktData[1]); // new value while(sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); *(quint16*)(pktData + 2) = qToBigEndian(quint16(~sum)); sendIp6Reply(pktBuf); qDebug("Sent ICMPv6 Echo Reply"); break; 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]; int len = pktBuf->length(); int minLen = 24 + (type == 136 ? 8 : 0); // NA should have the Target TLV 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 // TODO: Validation as per RFC 4861 sendNeighborAdvertisement(pktBuf); break; } case 136: { // Neigh Advt quint8 flags = pktData[4]; const quint8 kSFlag = 0x40; const quint8 kOFlag = 0x20; UInt128 tgtIp = qFromBigEndian(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(pktData + 26); mac = (mac << 16) | qFromBigEndian(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 void Device::sendNeighborSolicit(PacketBuffer *pktBuf) { uchar *pktData = pktBuf->data(); UInt128 srcIp = ip6_, dstIp, mask, tgtIp; if (pktBuf->length() < kIp6HdrLen) { qDebug("incomplete IPv6 header: expected %d, actual %d", kIp6HdrLen, pktBuf->length()); return; } dstIp = qFromBigEndian(pktData + 24); mask = ~UInt128(0, 0) << (128 - ip6PrefixLength_); qDebug("%s: dst %s src %s mask %s", __FUNCTION__, qPrintable(QHostAddress(dstIp.toArray()).toString()), qPrintable(QHostAddress(srcIp.toArray()).toString()), qPrintable(QHostAddress(mask.toArray()).toString())); tgtIp = ((dstIp & mask) == (srcIp & mask)) ? dstIp : ip6Gateway_; sendNeighborSolicit(tgtIp); } void Device::sendNeighborSolicit(UInt128 tgtIp) { UInt128 dstIp, srcIp = ip6_; PacketBuffer *reqPkt; uchar *pktData; // Validate target IP if (tgtIp == UInt128(0, 0)) return; // Do we already have a NDP entry (resolved or unresolved)? // If so, don't resend (see note in sendArpRequest()) if (ndpTable_.contains(tgtIp)) return; // Form the solicited node address to be used as dstIp // ff02::1:ffXX:XXXX/104 dstIp = UInt128((quint64(0xff02) << 48), (quint64(0x01ff) << 24) | (tgtIp.lo64() & 0xFFFFFF)); reqPkt = new PacketBuffer; reqPkt->reserve(encapSize() + kIp6HdrLen); pktData = reqPkt->put(32); if (pktData) { // Calculate checksum first - // start with fixed fields in ICMP Header and IPv6 Pseudo Header ... quint32 sum = 0x8700 + 0x0101 + 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(dstIp); while(sum >> 16) sum = (sum & 0xFFFF) + (sum >> 16); // Type, Code *(quint16*)(pktData ) = qToBigEndian(quint16(0x8700)); // Checksum *(quint16*)(pktData+ 2) = qToBigEndian(quint16(~sum)); // Reserved *(quint32*)(pktData+ 4) = qToBigEndian(quint32(0)); // Target IP memcpy(pktData+ 8, tgtIp.toArray(), 16); // Source Addr TLV + MacAddr *(quint16*)(pktData+24) = qToBigEndian(quint16(0x0101)); *(quint32*)(pktData+26) = qToBigEndian(quint32(mac_ >> 16)); *(quint16*)(pktData+30) = qToBigEndian(quint16(mac_ & 0xffff)); } if (!sendIp6(reqPkt, dstIp , kIpProtoIcmp6)) return; ndpTable_.insert(tgtIp, 0); qDebug("Sent NDP Request for srcIp/tgtIp=%s/%s", qPrintable(QHostAddress(srcIp.toArray()).toString()), 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)); } else if (pktBuf->length() >= 32) { // have TLVs? if ((pktData[24] == 0x01) && (pktData[25] == 0x01)) { // Source TLV quint64 mac; mac = qFromBigEndian(pktData + 26); mac = (mac << 16) | qFromBigEndian(pktData + 30); ndpTable_.insert(srcIp, mac); } } 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; while (i < a1.size()) { if (uchar(a1.at(i)) < uchar(a2.at(i))) return true; if (uchar(a1.at(i)) > uchar(a2.at(i))) return false; i++; } return false; }