init: arp lan scanner
This commit is contained in:
commit
96e75d545d
5 changed files with 177 additions and 0 deletions
14
.github/workflows/ci.yml
vendored
Normal file
14
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -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
|
||||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
build/
|
||||||
|
.claude/
|
||||||
|
*.obj
|
||||||
|
*.exe
|
||||||
|
*.pdb
|
||||||
|
*.ilk
|
||||||
11
CMakeLists.txt
Normal file
11
CMakeLists.txt
Normal file
|
|
@ -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()
|
||||||
29
README.md
Normal file
29
README.md
Normal file
|
|
@ -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
|
||||||
|
```
|
||||||
|
|
||||||
|
сам находит подсеть по активному адаптеру и сканирует её целиком.
|
||||||
117
src/main.cpp
Normal file
117
src/main.cpp
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <iphlpapi.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
#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<uint8_t> buf(size);
|
||||||
|
IP_ADAPTER_ADDRESSES* addrs = reinterpret_cast<IP_ADAPTER_ADDRESSES*>(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<sockaddr_in*>(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<std::mutex> 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<std::thread> 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;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue