/* * The MIT License (MIT) * * Copyright (c) 2017 Nathan Osman * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include #include #include #include #include #include #include namespace QMdnsEngine { template bool parseInteger(const QByteArray &packet, quint16 &offset, T &value) { if (offset + sizeof(T) > static_cast(packet.length())) { return false; // out-of-bounds } value = qFromBigEndian(reinterpret_cast(packet.constData() + offset)); offset += sizeof(T); return true; } template void writeInteger(QByteArray &packet, quint16 &offset, T value) { value = qToBigEndian(value); packet.append(reinterpret_cast(&value), sizeof(T)); offset += sizeof(T); } bool parseName(const QByteArray &packet, quint16 &offset, QByteArray &name) { quint16 offsetEnd = 0; quint16 offsetPtr = offset; forever { quint8 nBytes; if (!parseInteger(packet, offset, nBytes)) { return false; } if (!nBytes) { break; } switch (nBytes & 0xc0) { case 0x00: if (offset + nBytes > packet.length()) { return false; // length exceeds message } name.append(packet.mid(offset, nBytes)); name.append('.'); offset += nBytes; break; case 0xc0: { quint8 nBytes2; quint16 newOffset; if (!parseInteger(packet, offset, nBytes2)) { return false; } newOffset = ((nBytes & ~0xc0) << 8) | nBytes2; if (newOffset >= offsetPtr) { return false; // prevent infinite loop } offsetPtr = newOffset; if (!offsetEnd) { offsetEnd = offset; } offset = newOffset; break; } default: return false; // no other types supported } } if (offsetEnd) { offset = offsetEnd; } return true; } void writeName(QByteArray &packet, quint16 &offset, const QByteArray &name, QMap &nameMap) { QByteArray fragment = name; if (fragment.endsWith('.')) { fragment.chop(1); } while (fragment.length()) { if (nameMap.contains(fragment)) { writeInteger(packet, offset, nameMap.value(fragment) | 0xc000); return; } nameMap.insert(fragment, offset); int index = fragment.indexOf('.'); if (index == -1) { index = fragment.length(); } writeInteger(packet, offset, index); packet.append(fragment.left(index)); offset += index; fragment.remove(0, index + 1); } writeInteger(packet, offset, 0); } bool parseRecord(const QByteArray &packet, quint16 &offset, Record &record) { QByteArray name; quint16 type, class_, dataLen; quint32 ttl; if (!parseName(packet, offset, name) || !parseInteger(packet, offset, type) || !parseInteger(packet, offset, class_) || !parseInteger(packet, offset, ttl) || !parseInteger(packet, offset, dataLen)) { return false; } record.setName(name); record.setType(type); record.setFlushCache(class_ & 0x8000); record.setTtl(ttl); switch (type) { case A: { quint32 ipv4Addr; if (!parseInteger(packet, offset, ipv4Addr)) { return false; } record.setAddress(QHostAddress(ipv4Addr)); break; } case AAAA: { if (offset + 16 > packet.length()) { return false; } record.setAddress(QHostAddress( reinterpret_cast(packet.constData() + offset) )); offset += 16; break; } case NSEC: { QByteArray nextDomainName; quint8 number; quint8 length; if (!parseName(packet, offset, nextDomainName) || !parseInteger(packet, offset, number) || !parseInteger(packet, offset, length) || number != 0 || offset + length > packet.length()) { return false; } Bitmap bitmap; bitmap.setData(length, reinterpret_cast(packet.constData() + offset)); record.setNextDomainName(nextDomainName); record.setBitmap(bitmap); offset += length; break; } case PTR: { QByteArray target; if (!parseName(packet, offset, target)) { return false; } record.setTarget(target); break; } case SRV: { quint16 priority, weight, port; QByteArray target; if (!parseInteger(packet, offset, priority) || !parseInteger(packet, offset, weight) || !parseInteger(packet, offset, port) || !parseName(packet, offset, target)) { return false; } record.setPriority(priority); record.setWeight(weight); record.setPort(port); record.setTarget(target); break; } case TXT: { quint16 start = offset; while (offset < start + dataLen) { quint8 nBytes; if (!parseInteger(packet, offset, nBytes) || offset + nBytes > packet.length()) { return false; } if (nBytes == 0) { break; } QByteArray attr(packet.constData() + offset, nBytes); offset += nBytes; int splitIndex = attr.indexOf('='); if (splitIndex == -1) { record.addAttribute(attr, QByteArray()); } else { record.addAttribute(attr.left(splitIndex), attr.mid(splitIndex + 1)); } } break; } default: offset += dataLen; break; } return true; } void writeRecord(QByteArray &packet, quint16 &offset, Record &record, QMap &nameMap) { writeName(packet, offset, record.name(), nameMap); writeInteger(packet, offset, record.type()); writeInteger(packet, offset, record.flushCache() ? 0x8001 : 1); writeInteger(packet, offset, record.ttl()); offset += 2; QByteArray data; switch (record.type()) { case A: writeInteger(data, offset, record.address().toIPv4Address()); break; case AAAA: { Q_IPV6ADDR ipv6Addr = record.address().toIPv6Address(); data.append(reinterpret_cast(&ipv6Addr), sizeof(Q_IPV6ADDR)); offset += data.length(); break; } case NSEC: { quint8 length = record.bitmap().length(); writeName(data, offset, record.nextDomainName(), nameMap); writeInteger(data, offset, 0); writeInteger(data, offset, length); data.append(reinterpret_cast(record.bitmap().data()), length); offset += length; break; } case PTR: writeName(data, offset, record.target(), nameMap); break; case SRV: writeInteger(data, offset, record.priority()); writeInteger(data, offset, record.weight()); writeInteger(data, offset, record.port()); writeName(data, offset, record.target(), nameMap); break; case TXT: if (!record.attributes().count()) { writeInteger(data, offset, 0); break; } for (auto i = record.attributes().constBegin(); i != record.attributes().constEnd(); ++i) { QByteArray entry = i.value().isNull() ? i.key() : i.key() + "=" + i.value(); writeInteger(data, offset, entry.length()); data.append(entry); offset += entry.length(); } break; default: break; } offset -= 2; writeInteger(packet, offset, data.length()); packet.append(data); } bool fromPacket(const QByteArray &packet, Message &message) { quint16 offset = 0; quint16 transactionId, flags, nQuestion, nAnswer, nAuthority, nAdditional; if (!parseInteger(packet, offset, transactionId) || !parseInteger(packet, offset, flags) || !parseInteger(packet, offset, nQuestion) || !parseInteger(packet, offset, nAnswer) || !parseInteger(packet, offset, nAuthority) || !parseInteger(packet, offset, nAdditional)) { return false; } message.setTransactionId(transactionId); message.setResponse(flags & 0x8400); message.setTruncated(flags & 0x0200); for (int i = 0; i < nQuestion; ++i) { QByteArray name; quint16 type, class_; if (!parseName(packet, offset, name) || !parseInteger(packet, offset, type) || !parseInteger(packet, offset, class_)) { return false; } Query query; query.setName(name); query.setType(type); query.setUnicastResponse(class_ & 0x8000); message.addQuery(query); } quint16 nRecord = nAnswer + nAuthority + nAdditional; for (int i = 0; i < nRecord; ++i) { Record record; if (!parseRecord(packet, offset, record)) { return false; } message.addRecord(record); } return true; } void toPacket(const Message &message, QByteArray &packet) { quint16 offset = 0; quint16 flags = (message.isResponse() ? 0x8400 : 0) | (message.isTruncated() ? 0x200 : 0); writeInteger(packet, offset, message.transactionId()); writeInteger(packet, offset, flags); writeInteger(packet, offset, message.queries().length()); writeInteger(packet, offset, message.records().length()); writeInteger(packet, offset, 0); writeInteger(packet, offset, 0); QMap nameMap; const auto queries = message.queries(); for (const Query &query : queries) { writeName(packet, offset, query.name(), nameMap); writeInteger(packet, offset, query.type()); writeInteger(packet, offset, query.unicastResponse() ? 0x8001 : 1); } const auto records = message.records(); for (Record record : records) { writeRecord(packet, offset, record, nameMap); } } QString typeName(quint16 type) { switch (type) { case A: return "A"; case AAAA: return "AAAA"; case ANY: return "ANY"; case NSEC: return "NSEC"; case PTR: return "PTR"; case SRV: return "SRV"; case TXT: return "TXT"; default: return "?"; } } }