From 96e75d545dc977c9d5e3da0184f818d5820b1dad Mon Sep 17 00:00:00 2001 From: ImSavsis Date: Sun, 19 Jul 2026 20:27:07 +0500 Subject: [PATCH] init: arp lan scanner --- .github/workflows/ci.yml | 14 +++++ .gitignore | 6 ++ CMakeLists.txt | 11 ++++ README.md | 29 ++++++++++ src/main.cpp | 117 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 177 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 src/main.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c4b2583 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,14 @@ +name: ci + +on: + push: + branches: [master] + +jobs: + build: + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: ilammy/msvc-dev-cmd@v1 + - run: cmake -B build + - run: cmake --build build --config Release diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d42690b --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +build/ +.claude/ +*.obj +*.exe +*.pdb +*.ilk diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0b922cb --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.16) +project(lanscan CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(lanscan src/main.cpp) + +if(WIN32) + target_link_libraries(lanscan ws2_32 iphlpapi) +endif() diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee971f9 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# lanscan + +ARP-сканер локальной сети на C++. находит свою подсеть сам, шлёт ARP-запросы всем хостам параллельно, печатает кто ответил. + +```mermaid +sequenceDiagram + lanscan->>OS: узнать свой IP + маску + loop каждый хост в подсети + lanscan->>Host: ARP-запрос (кто ты?) + Host-->>lanscan: MAC-адрес (если жив) + end +``` + +через `SendARP` из iphlpapi — без сырых сокетов и драйверов, работает без админа. + +## сборка + +``` +cmake -B build +cmake --build build --config Release +``` + +## юзать + +``` +build\Release\lanscan.exe +``` + +сам находит подсеть по активному адаптеру и сканирует её целиком. diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..04b0beb --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "iphlpapi.lib") + +struct LocalNet { + uint32_t ip = 0; + uint32_t mask = 0; + bool found = false; +}; + +static LocalNet find_local_net() { + LocalNet net; + ULONG size = 0; + GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, nullptr, &size); + std::vector buf(size); + IP_ADAPTER_ADDRESSES* addrs = reinterpret_cast(buf.data()); + if (GetAdaptersAddresses(AF_INET, GAA_FLAG_INCLUDE_PREFIX, nullptr, addrs, &size) != NO_ERROR) { + return net; + } + + for (IP_ADAPTER_ADDRESSES* a = addrs; a != nullptr; a = a->Next) { + if (a->OperStatus != IfOperStatusUp) continue; + if (a->IfType == IF_TYPE_SOFTWARE_LOOPBACK) continue; + + for (IP_ADAPTER_UNICAST_ADDRESS* ua = a->FirstUnicastAddress; ua != nullptr; ua = ua->Next) { + sockaddr_in* sin = reinterpret_cast(ua->Address.lpSockaddr); + uint32_t ip = ntohl(sin->sin_addr.s_addr); + if ((ip >> 24) == 127) continue; + + uint8_t prefix = ua->OnLinkPrefixLength; + if (prefix == 0 || prefix > 32) continue; + + uint32_t mask = prefix == 32 ? 0xFFFFFFFFu : (~0u << (32 - prefix)); + net.ip = ip; + net.mask = mask; + net.found = true; + return net; + } + } + return net; +} + +static std::mutex out_mutex; + +static void probe(uint32_t ip_host_order) { + uint32_t target = htonl(ip_host_order); + uint8_t mac[6]; + ULONG mac_len = 6; + + DWORD res = SendARP(target, 0, mac, &mac_len); + if (res != NO_ERROR) return; + + in_addr addr{}; + addr.s_addr = target; + char ipstr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &addr, ipstr, sizeof(ipstr)); + + char macstr[32]; + snprintf(macstr, sizeof(macstr), "%02X:%02X:%02X:%02X:%02X:%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + std::lock_guard lock(out_mutex); + std::cout << ipstr << "\t" << macstr << std::endl; +} + +int main() { + WSADATA wsa; + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { + std::cerr << "WSAStartup failed" << std::endl; + return 1; + } + + LocalNet net = find_local_net(); + if (!net.found) { + std::cerr << "no local ipv4 network found" << std::endl; + WSACleanup(); + return 1; + } + + uint32_t network = net.ip & net.mask; + uint32_t broadcast = network | ~net.mask; + uint32_t host_count = broadcast - network; + + if (host_count == 0 || host_count > 65534) { + std::cerr << "subnet too big or too small to scan (" << host_count << " hosts)" << std::endl; + WSACleanup(); + return 1; + } + + in_addr netaddr{}; + netaddr.s_addr = htonl(network); + char netstr[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &netaddr, netstr, sizeof(netstr)); + std::cout << "scanning " << netstr << "/" << host_count << " hosts..." << std::endl; + + std::vector threads; + for (uint32_t ip = network + 1; ip < broadcast; ++ip) { + threads.emplace_back(probe, ip); + if (threads.size() >= 64) { + for (auto& t : threads) t.join(); + threads.clear(); + } + } + for (auto& t : threads) t.join(); + + WSACleanup(); + return 0; +}