/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2021, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. * This project including source code and documentation is protected by * copyright law, and remains the property of nymea GmbH. All rights, including * reproduction, publication, editing and translation, are reserved. The use of * this project is subject to the terms of a license agreement to be concluded * with nymea GmbH in accordance with the terms of use of nymea GmbH, available * under https://nymea.io/license * * GNU Lesser General Public License Usage * Alternatively, this project may be redistributed and/or modified under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation; version 3. This project 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this project. If not, see . * * For any further details and any questions please contact us under * contact@nymea.io or see our FAQ/Licensing Information on * https://nymea.io/license/faq * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "arpsocket.h" #include "loggingcategories.h" #include "networkutils.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()) << "Open ARP socket..."; 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 = NetworkUtils::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); } }