From 7b4a13be75c3528eb6023ad18b58b2a33a8788be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 4 Jun 2021 23:21:35 +0200 Subject: [PATCH] Add ARP socket --- libnymea/libnymea.pro | 2 + libnymea/network/arpsocket.cpp | 393 +++++++++++++++++++++++++++++++++ libnymea/network/arpsocket.h | 54 +++++ 3 files changed, 449 insertions(+) create mode 100644 libnymea/network/arpsocket.cpp create mode 100644 libnymea/network/arpsocket.h diff --git a/libnymea/libnymea.pro b/libnymea/libnymea.pro index 2c9ea6c0..411dcf87 100644 --- a/libnymea/libnymea.pro +++ b/libnymea/libnymea.pro @@ -42,6 +42,7 @@ HEADERS += \ network/apikeys/apikey.h \ network/apikeys/apikeysprovider.h \ network/apikeys/apikeystorage.h \ + network/arpsocket.h \ network/ping.h \ network/pingreply.h \ platform/package.h \ @@ -142,6 +143,7 @@ SOURCES += \ network/apikeys/apikey.cpp \ network/apikeys/apikeysprovider.cpp \ network/apikeys/apikeystorage.cpp \ + network/arpsocket.cpp \ network/ping.cpp \ network/pingreply.cpp \ nymeasettings.cpp \ diff --git a/libnymea/network/arpsocket.cpp b/libnymea/network/arpsocket.cpp new file mode 100644 index 00000000..0543eeda --- /dev/null +++ b/libnymea/network/arpsocket.cpp @@ -0,0 +1,393 @@ +#include "arpsocket.h" +#include "loggingcategories.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +NYMEA_LOGGING_CATEGORY(dcArpSocket, "ArpSocket") + +#define ETHER_PROTOCOL_LEN 4 // Length of the IPv4 address +#define ETHER_HEADER_LEN sizeof(struct ether_header) +#define ETHER_ARP_LEN sizeof(struct ether_arp) +#define ETHER_ARP_PACKET_LEN ETHER_HEADER_LEN + ETHER_ARP_LEN + +ArpSocket::ArpSocket(QObject *parent) : QObject(parent) +{ + +} + +bool ArpSocket::sendRequest() +{ + if (!m_isOpen) + return false; + + // Send the ARP request trough each network interface + qCDebug(dcArpSocket()) << "Sending ARP request to all local network interfaces..."; + foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { + sendRequest(networkInterface); + } + + return true; +} + +bool ArpSocket::sendRequest(const QString &interfaceName) +{ + if (!m_isOpen) + return false; + + // Get the interface + qCDebug(dcArpSocket()) << "Sending ARP request to all network interfaces" << interfaceName << "..."; + QNetworkInterface networkInterface = QNetworkInterface::interfaceFromName(interfaceName); + if (!networkInterface.isValid()) { + qCWarning(dcArpSocket()) << "Failed to send the ARP request to network interface" << interfaceName << "because the interface is not valid."; + return false; + } + + return sendRequest(networkInterface); +} + +bool ArpSocket::sendRequest(const QNetworkInterface &networkInterface) +{ + if (!m_isOpen) + return false; + + // Skip local host + if (networkInterface.flags().testFlag(QNetworkInterface::IsLoopBack)) + return false; + + // If have no interface indes, we cannot use this network + if (networkInterface.index() == 0) { + qCDebug(dcArpSocket()) << "Failed to send the ARP request to network interface" << networkInterface.name() << "because the system interface index is unknown."; + return false; + } + + // Check if the interface is up and running + if (!networkInterface.flags().testFlag(QNetworkInterface::IsUp)) { + qCDebug(dcArpSocket()) << "Failed to send the ARP request to network interface" << networkInterface.name() << "because it is not up."; + return false; + } + + if (!networkInterface.flags().testFlag(QNetworkInterface::IsRunning)) { + qCDebug(dcArpSocket()) << "Failed to send the ARP request to network interface" << networkInterface.name() << "because it is not running."; + return false; + } + + // Verify we have a hardware address (virtual network interfaces like tunnels) + if (networkInterface.hardwareAddress().isEmpty()) { + qCDebug(dcArpSocket()) << "Failed to send the ARP request to network interface" << networkInterface.name() << "because there is no hardware address which is required for ARP."; + return false; + } + + qCDebug(dcArpSocket()) << "Verifying network interface" << networkInterface.name() << networkInterface.hardwareAddress() << "..."; + foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) { + // Only IPv4 + if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol) + continue; + + qCDebug(dcArpSocket()) << " Host address:" << entry.ip().toString(); + qCDebug(dcArpSocket()) << " Broadcast address:" << entry.broadcast().toString(); + qCDebug(dcArpSocket()) << " Netmask:" << entry.netmask().toString(); + quint32 addressRangeStart = entry.ip().toIPv4Address() & entry.netmask().toIPv4Address(); + quint32 addressRangeStop = entry.broadcast().toIPv4Address() | addressRangeStart; + quint32 range = addressRangeStop - addressRangeStart; + qCDebug(dcArpSocket()) << " Address range" << range << " | from" << QHostAddress(addressRangeStart).toString() << "-->" << QHostAddress(addressRangeStop).toString(); + if (range > 255) { + qCWarning(dcArpSocket()) << "Not sending ARP requests to the network" << networkInterface.name() << "because it has a to wide range for ARP broadcast pinging."; + return false; + } + + qCDebug(dcArpSocket()) << "Start sending ARP requests to each host within the range..."; + + // Send ARP request to each address within the range + for (quint32 i = 0; i < range; i++) { + quint32 address = addressRangeStart + i; + QHostAddress targetAddress(address); + if (targetAddress == entry.ip()) + continue; + + sendRequestInternally(networkInterface.index(), networkInterface.hardwareAddress(), entry.ip(), "ff:ff:ff:ff:ff:ff", targetAddress); + } + } + + return true; +} + +bool ArpSocket::sendRequest(const QHostAddress &targetAddress) +{ + if (!m_isOpen) + return false; + + if (targetAddress.protocol() != QAbstractSocket::IPv4Protocol) { + qCWarning(dcArpSocket()) << "Not sending ARP request to host" << targetAddress << "because only IPv4 is supported."; + return false; + } + + qCDebug(dcArpSocket()) << "Sending ARP request to host" << targetAddress.toString() << "..."; + foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { + foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) { + // Only IPv4 + if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol) + continue; + + if (targetAddress.isInSubnet(entry.ip(), entry.netmask().toIPv4Address())) { + return sendRequestInternally(networkInterface.index(), networkInterface.hardwareAddress(), entry.ip(), "ff:ff:ff:ff:ff:ff", targetAddress); + } + } + } + + qCWarning(dcArpSocket()) << "Failed to send ARP request to" << targetAddress.toString() << "because no valid network interface could be found."; + return false; +} + +bool ArpSocket::isOpen() const +{ + return m_isOpen; +} + +bool ArpSocket::openSocket() +{ + qCDebug(dcArpSocket()) << "Enabeling ARP scanner..."; + + if (m_isOpen) { + qCWarning(dcArpSocket()) << "Failed to enable ARP scanner because the scanner is already running."; + return false; + } + + // Build socket descriptor + m_socketDescriptor = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ARP)); + if (m_socketDescriptor < 0) { + qCWarning(dcArpSocket()) << "Failed to create the ARP capture socket for" << "." << strerror(errno); + return false; + } + + // Configure non blocking + if (fcntl(m_socketDescriptor, F_SETFL, fcntl(m_socketDescriptor, F_GETFL, 0) | O_NONBLOCK) != 0) { + qCWarning(dcArpSocket()) << "Failed to set the ARP socket function control to non-blocking" << strerror(errno); + close(m_socketDescriptor); + return false; + } + + m_socketNotifier = new QSocketNotifier(m_socketDescriptor, QSocketNotifier::Read, this); + m_socketNotifier->setEnabled(false); + connect(m_socketNotifier, &QSocketNotifier::activated, this, [=](int socket){ + if (socket != m_socketDescriptor) + return; + + // Make sure to read all data from the socket... + while (true) { + char receiveBuffer[ETHER_ARP_PACKET_LEN]; + memset(&receiveBuffer, 0, sizeof(receiveBuffer)); + + // Read the buffer + int bytesReceived = recv(m_socketDescriptor, receiveBuffer, ETHER_ARP_PACKET_LEN, 0); + if (bytesReceived < 0) { + // Finished reading + return; + } + + // Parse data using structs header + arp + struct ether_header *etherHeader = (struct ether_header *)(receiveBuffer); + struct ether_arp *arpPacket = (struct ether_arp *)(receiveBuffer + ETHER_HEADER_LEN); + QString senderMacAddress = getMacAddressString(arpPacket->arp_sha); + QHostAddress senderHostAddress = getHostAddressString(arpPacket->arp_spa); + QString targetMacAddress = getMacAddressString(arpPacket->arp_tha); + QHostAddress targetHostAddress = getHostAddressString(arpPacket->arp_tpa); + uint16_t etherType = htons(etherHeader->ether_type); + if (etherType != ETHERTYPE_ARP) { + qCWarning(dcArpSocket()) << "Received ARP socket data header with invalid type" << etherType; + return; + } + + // Filter for ARP replies + uint16_t arpOperationCode = htons(arpPacket->arp_op); + switch (arpOperationCode) { + case ARPOP_REQUEST: + //qCDebug(dcArpSocket()) << "ARP request from " << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString(); + break; + case ARPOP_REPLY: { + QNetworkInterface networkInterface = getInterfaceForMacAddress(targetMacAddress); + if (!networkInterface.isValid()) { + qCWarning(dcArpSocket()) << "Could not find interface from ARP response" << targetHostAddress.toString() << targetMacAddress; + return; + } + + qCDebug(dcArpSocket()) << "ARP response from" << senderMacAddress << senderHostAddress.toString() << "on" << networkInterface.name(); + emit arpResponse(networkInterface, senderHostAddress, senderMacAddress.toLower()); + break; + } + case ARPOP_RREQUEST: + //qCDebug(dcArpSocket()) << "RARP request from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString(); + break; + case ARPOP_RREPLY: + //qCDebug(dcArpSocket()) << "PARP response from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString(); + break; + case ARPOP_InREQUEST: + //qCDebug(dcArpSocket()) << "InARP request from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString(); + break; + case ARPOP_InREPLY: + //qCDebug(dcArpSocket()) << "InARP response from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString(); + break; + case ARPOP_NAK: + //qCDebug(dcArpSocket()) << "(ATM)ARP NAK from" << senderMacAddress << senderHostAddress.toString() << "-->" << targetMacAddress << targetHostAddress.toString(); + break; + default: + qCWarning(dcArpSocket()) << "Received unhandled ARP operation code" << arpOperationCode << "from" << senderMacAddress << senderHostAddress.toString(); + break; + } + } + }); + + m_socketNotifier->setEnabled(true); + m_isOpen = true; + qCDebug(dcArpSocket()) << "ARP enabled successfully"; + + // Send broadcast request + //sendRequest(); + return true; +} + +void ArpSocket::closeSocket() +{ + m_isOpen = false; + + if (m_socketNotifier) { + m_socketNotifier->setEnabled(false); + delete m_socketNotifier; + m_socketNotifier = nullptr; + } + + if (m_socketDescriptor >= 0) { + close(m_socketDescriptor); + m_socketDescriptor = -1; + } + + qCDebug(dcArpSocket()) << "ARP disabled successfully"; +} + +bool ArpSocket::sendRequestInternally(int networkInterfaceIndex, const QString &senderMacAddress, const QHostAddress &senderHostAddress, const QString &targetMacAddress, const QHostAddress &targetHostAddress) +{ + // Set up data structures + unsigned char sendingBuffer[ETHER_ARP_PACKET_LEN]; + memset(sendingBuffer, 0, ETHER_ARP_PACKET_LEN); + struct ether_header *etherHeader = (struct ether_header *)sendingBuffer; + struct ether_arp *arpPacket = (struct ether_arp *)(sendingBuffer + sizeof(struct ether_header)); + + // Build the ethernet header + fillMacAddress(etherHeader->ether_dhost, targetMacAddress); + fillMacAddress(etherHeader->ether_shost, senderMacAddress); + etherHeader->ether_type = htons(ETHERTYPE_ARP); + + // Build the ARP header + arpPacket->ea_hdr.ar_hrd = htons(ARPHRD_ETHER); + arpPacket->ea_hdr.ar_pro = htons(ETH_P_IP); + arpPacket->ea_hdr.ar_hln = ETHER_ADDR_LEN; + arpPacket->ea_hdr.ar_pln = ETHER_PROTOCOL_LEN; + arpPacket->ea_hdr.ar_op = htons(ARPOP_REQUEST); + + // Write the ARP packet + fillMacAddress(arpPacket->arp_sha, senderMacAddress); + fillHostAddress(arpPacket->arp_spa, senderHostAddress); + fillMacAddress(arpPacket->arp_tha, targetMacAddress); + fillHostAddress(arpPacket->arp_tpa, targetHostAddress); + + struct sockaddr_ll socketAddress; + memset(&socketAddress, 0, sizeof(socketAddress)); + socketAddress.sll_family = AF_PACKET; + socketAddress.sll_protocol = htons(ETH_P_ARP); + socketAddress.sll_ifindex = networkInterfaceIndex; + socketAddress.sll_hatype = htons(ARPHRD_ETHER); + socketAddress.sll_pkttype = PACKET_BROADCAST; + socketAddress.sll_halen = ETH_ALEN; + memset(socketAddress.sll_addr, 0x00, 6); + + //qCDebug(dcArpSocket()) << "Send ARP request to" << targetHostAddress.toString(); + int bytesSent = sendto(m_socketDescriptor, sendingBuffer, ETHER_ARP_PACKET_LEN, 0, (struct sockaddr *)&socketAddress, sizeof(socketAddress)); + if (bytesSent < 0) { + qCWarning(dcArpSocket()) << "Failed to send ARP packet data to" << targetHostAddress.toString() << strerror(errno); + return false; + } + + return true; +} + +QString ArpSocket::getMacAddressString(uint8_t *senderHardwareAddress) +{ + QStringList hexValues; + for (int i = 0; i < ETHER_ADDR_LEN; i++) { + hexValues.append(QString("%1").arg(senderHardwareAddress[i], 2, 16, QLatin1Char('0'))); + } + + return hexValues.join(":"); +} + +QHostAddress ArpSocket::getHostAddressString(uint8_t *senderIpAddress) +{ + QStringList values; + for (int i = 0; i < ETHER_PROTOCOL_LEN; i++) { + values.append(QString("%1").arg(senderIpAddress[i])); + } + + return QHostAddress(values.join(".")); +} + +void ArpSocket::fillMacAddress(uint8_t *targetArray, const QString &macAddress) +{ + QStringList macValues = macAddress.split(":"); + for (int i = 0; i < ETHER_ADDR_LEN; i++) { + targetArray[i] = macValues.at(i).toUInt(nullptr, 16); + } +} + +void ArpSocket::fillHostAddress(uint8_t *targetArray, const QHostAddress &hostAddress) +{ + QByteArray hostData; + QDataStream stream(&hostData, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::BigEndian); + stream << hostAddress.toIPv4Address(); + for (int i = 0; i < ETHER_PROTOCOL_LEN; i++) { + targetArray[i] = hostData.at(i); + } +} + +QNetworkInterface ArpSocket::getInterfaceForHostaddress(const QHostAddress &address) +{ + foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { + foreach (const QNetworkAddressEntry &entry, networkInterface.addressEntries()) { + // Only IPv4 + if (entry.ip().protocol() != QAbstractSocket::IPv4Protocol) + continue; + + if (address.isInSubnet(entry.ip(), entry.netmask().toIPv4Address())) { + return networkInterface; + } + } + } + + return QNetworkInterface(); +} + +QNetworkInterface ArpSocket::getInterfaceForMacAddress(const QString &macAddress) +{ + foreach (const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { + if (networkInterface.hardwareAddress().toLower() == macAddress.toLower()) { + return networkInterface; + } + } + + return QNetworkInterface(); +} diff --git a/libnymea/network/arpsocket.h b/libnymea/network/arpsocket.h new file mode 100644 index 00000000..d910d27e --- /dev/null +++ b/libnymea/network/arpsocket.h @@ -0,0 +1,54 @@ +#ifndef ARPSOCKET_H +#define ARPSOCKET_H + +#include +#include +#include +#include +#include +#include + +class ArpSocket : public QObject +{ + Q_OBJECT +public: + explicit ArpSocket(QObject *parent = nullptr); + + // Send ARP request to all local networks + bool sendRequest(); + + // Send ARP request to a specific network interface with the given name + bool sendRequest(const QString &interfaceName); + + // Send ARP request to a specific network interface + bool sendRequest(const QNetworkInterface &networkInterface); + + // Send ARP request to a specific address within the network + bool sendRequest(const QHostAddress &targetAddress); + + bool isOpen() const; + + bool openSocket(); + void closeSocket(); + +signals: + void arpResponse(const QNetworkInterface &networkInterface, const QHostAddress &address, const QString &macAddress); + +private: + QSocketNotifier *m_socketNotifier = nullptr; + int m_socketDescriptor = -1; + bool m_isOpen = false; + + bool sendRequestInternally(int networkInterfaceIndex, const QString &senderMacAddress, const QHostAddress &senderHostAddress, const QString &targetMacAddress, const QHostAddress &targetHostAddress); + + QString getMacAddressString(uint8_t *senderHardwareAddress); + QHostAddress getHostAddressString(uint8_t *senderIpAddress); + + void fillMacAddress(uint8_t *targetArray, const QString &macAddress); + void fillHostAddress(uint8_t *targetArray, const QHostAddress &hostAddress); + + QNetworkInterface getInterfaceForHostaddress(const QHostAddress &address); + QNetworkInterface getInterfaceForMacAddress(const QString &macAddress); +}; + +#endif // ARPSOCKET_H