Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7af9a49eef | |||
| 4f325fead2 | |||
| 5ac2995b2d | |||
| 8016d570b8 |
@@ -0,0 +1,30 @@
|
|||||||
|
#include "packet_sniffer_cli.hpp"
|
||||||
|
#include <boost/program_options/value_semantic.hpp>
|
||||||
|
|
||||||
|
PacketSnifferCli::PacketSnifferCli()
|
||||||
|
: CliBase("Packet Sniffer Options")
|
||||||
|
, ip_("")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void PacketSnifferCli::setupOptions()
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
desc_.add_options()
|
||||||
|
("help,h", "Show help")
|
||||||
|
("ip,H", withDefault(ip_, "<IP>"), "Local IPv4 to sniff (e.g. 192.168.1.42)")
|
||||||
|
("mask,m", boost::program_options::value<bool>()->default_value(false)->implicit_value(true),
|
||||||
|
"Mask IP addresses in output (demo)");
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PacketSnifferCli::getIp() const
|
||||||
|
{
|
||||||
|
return vm_.at("ip").as<std::string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PacketSnifferCli::isMaskEnabled() const
|
||||||
|
{
|
||||||
|
const auto it = vm_.find("mask");
|
||||||
|
return (it != vm_.end()) ? it->second.as<bool>() : false;
|
||||||
|
}
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "cli_base.hpp"
|
#include "cli_base.hpp"
|
||||||
#include "option_utils.hpp"
|
#include "option_utils.hpp"
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class PortScanCli : public CliBase, protected OptionUtils
|
class PacketSnifferCli : public CliBase, protected OptionUtils
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PortScanCli();
|
PacketSnifferCli();
|
||||||
|
|
||||||
std::string getIp() const;
|
std::string getIp() const;
|
||||||
|
bool isMaskEnabled() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void setupOptions() override;
|
void setupOptions() override;
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#include "portscan_cli.hpp"
|
|
||||||
#include <boost/program_options/value_semantic.hpp>
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
PortScanCli::PortScanCli()
|
|
||||||
: CliBase("Port Scanner Options")
|
|
||||||
, ip_("")
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void PortScanCli::setupOptions()
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
desc_.add_options()
|
|
||||||
("help,h", "Show help")
|
|
||||||
("ip,H", withDefault(ip_, "<IP>"), "Local IPv4 to scan");
|
|
||||||
// clang-format on
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string PortScanCli::getIp() const
|
|
||||||
{
|
|
||||||
if (vm_["ip"].empty() == 0) {
|
|
||||||
return "127.0.0.1";
|
|
||||||
}
|
|
||||||
return vm_.at("ip").as<std::string>();
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
#include "port_scanner.hpp"
|
|
||||||
#include "logger.hpp"
|
|
||||||
#include <boost/asio.hpp>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
PortScanner::PortScanner(
|
|
||||||
const std::string &ip, const std::vector<uint16_t> &ports
|
|
||||||
)
|
|
||||||
: ports_(ports)
|
|
||||||
{
|
|
||||||
ErrorCode ec;
|
|
||||||
addr_ = boost::asio::ip::make_address(ip, ec);
|
|
||||||
ip_ok_ = !ec;
|
|
||||||
|
|
||||||
if (!ip_ok_) {
|
|
||||||
Logger::error(std::string("PortScanner: invalid IP: ") + ip);
|
|
||||||
} else {
|
|
||||||
Logger::debug(std::string("PortScanner: using IP: ") + ip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PortScanner::setTimeoutMs(int ms)
|
|
||||||
{
|
|
||||||
if (ms > 0) {
|
|
||||||
timeout_ms_ = ms;
|
|
||||||
Logger::debug(
|
|
||||||
std::string("PortScanner: timeout set to ")
|
|
||||||
+ std::to_string(timeout_ms_) + " ms"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PortScanner::run()
|
|
||||||
{
|
|
||||||
if (!ip_ok_) {
|
|
||||||
Logger::error("PortScanner::run - aborting: ip not ok");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (uint16_t p : ports_) {
|
|
||||||
if (probe(p)) {
|
|
||||||
Logger::info(std::to_string(p));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PortScanner::probe(uint16_t port)
|
|
||||||
{
|
|
||||||
TcpSocket s(io_);
|
|
||||||
SteadyTimer t(io_);
|
|
||||||
auto ep = endpoint(port);
|
|
||||||
|
|
||||||
Logger::debug(
|
|
||||||
std::string("PortScanner::probe - probing port ") + std::to_string(port)
|
|
||||||
);
|
|
||||||
|
|
||||||
armTimer(t, s);
|
|
||||||
armConnect(s, ep);
|
|
||||||
runUntilDone();
|
|
||||||
cancelTimer(t);
|
|
||||||
drainLeftovers();
|
|
||||||
close(s);
|
|
||||||
|
|
||||||
if (ok_) {
|
|
||||||
Logger::debug(std::string("Port ") + std::to_string(port) + " is OPEN");
|
|
||||||
} else {
|
|
||||||
Logger::debug(
|
|
||||||
std::string("Port ") + std::to_string(port) + " is CLOSED/FILTERED"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ok_;
|
|
||||||
}
|
|
||||||
|
|
||||||
TcpEndpoint PortScanner::endpoint(uint16_t port) const
|
|
||||||
{
|
|
||||||
return TcpEndpoint(addr_, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PortScanner::armTimer(SteadyTimer &t, TcpSocket &s)
|
|
||||||
{
|
|
||||||
t.expires_after(std::chrono::milliseconds(timeout_ms_));
|
|
||||||
|
|
||||||
auto onTimeout = [&s](const ErrorCode &ec) {
|
|
||||||
if (!ec) {
|
|
||||||
Logger::debug("Timer expired -> cancelling socket");
|
|
||||||
s.cancel();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
t.async_wait(onTimeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PortScanner::armConnect(TcpSocket &s, const TcpEndpoint &ep)
|
|
||||||
{
|
|
||||||
ok_ = false;
|
|
||||||
done_ = false;
|
|
||||||
|
|
||||||
auto onConnect = [this](const ErrorCode &ec) {
|
|
||||||
if (!ec) {
|
|
||||||
ok_ = true;
|
|
||||||
Logger::debug("async_connect succeeded");
|
|
||||||
} else {
|
|
||||||
ok_ = false;
|
|
||||||
Logger::debug(std::string("async_connect failed: ") + ec.message());
|
|
||||||
}
|
|
||||||
done_ = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
s.async_connect(ep, onConnect);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PortScanner::runUntilDone()
|
|
||||||
{
|
|
||||||
io_.restart();
|
|
||||||
while (!done_ && io_.run_one()) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PortScanner::cancelTimer(SteadyTimer &t)
|
|
||||||
{
|
|
||||||
ErrorCode ec;
|
|
||||||
t.cancel();
|
|
||||||
if (ec) {
|
|
||||||
Logger::warn(std::string("cancelTimer: ") + ec.message());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PortScanner::drainLeftovers()
|
|
||||||
{
|
|
||||||
io_.restart();
|
|
||||||
while (io_.run_one()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PortScanner::close(TcpSocket &s)
|
|
||||||
{
|
|
||||||
ErrorCode ig;
|
|
||||||
if (s.close(ig)) {
|
|
||||||
Logger::warn(std::string("close socket failed: ") + ig.message());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "aliases.hpp"
|
|
||||||
#include <boost/asio.hpp>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class PortScanner
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PortScanner(const std::string &ip, const std::vector<uint16_t> &ports);
|
|
||||||
|
|
||||||
void setTimeoutMs(int ms);
|
|
||||||
|
|
||||||
void run();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool probe(uint16_t port);
|
|
||||||
TcpEndpoint endpoint(uint16_t port) const;
|
|
||||||
|
|
||||||
void armTimer(SteadyTimer &t, TcpSocket &s);
|
|
||||||
void armConnect(TcpSocket &s, const TcpEndpoint &ep);
|
|
||||||
void runUntilDone();
|
|
||||||
void cancelTimer(SteadyTimer &t);
|
|
||||||
void drainLeftovers();
|
|
||||||
void close(TcpSocket &s);
|
|
||||||
|
|
||||||
private:
|
|
||||||
IoContext io_;
|
|
||||||
IpAddress addr_;
|
|
||||||
std::vector<uint16_t> ports_;
|
|
||||||
|
|
||||||
int timeout_ms_{300};
|
|
||||||
bool ip_ok_{false};
|
|
||||||
bool ok_{false};
|
|
||||||
bool done_{false};
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
#include "sniffer.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
#include <tins/tins.h>
|
||||||
|
|
||||||
|
Sniffer::Sniffer(std::string localIp, bool maskIp)
|
||||||
|
: ip_(std::move(localIp))
|
||||||
|
, mask_(maskIp)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int Sniffer::run()
|
||||||
|
{
|
||||||
|
const auto ifaceName = resolveIfaceNameByIp(ip_);
|
||||||
|
|
||||||
|
Tins::SnifferConfiguration cfg;
|
||||||
|
cfg.set_promisc_mode(true);
|
||||||
|
cfg.set_immediate_mode(true);
|
||||||
|
|
||||||
|
Tins::Sniffer sniffer(ifaceName, cfg);
|
||||||
|
|
||||||
|
auto running = [this] { return !stop_.load(std::memory_order_relaxed); };
|
||||||
|
|
||||||
|
auto handler = [&](Tins::PDU &pdu) -> bool {
|
||||||
|
if (!running())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string src, dst;
|
||||||
|
const char *type = "OTHER";
|
||||||
|
uint16_t sport = 0, dport = 0;
|
||||||
|
|
||||||
|
if (auto *arp = pdu.find_pdu<Tins::ARP>()) {
|
||||||
|
src = arp->sender_ip_addr().to_string();
|
||||||
|
dst = arp->target_ip_addr().to_string();
|
||||||
|
type = "ARP";
|
||||||
|
} else if (auto *ip4 = pdu.find_pdu<Tins::IP>()) {
|
||||||
|
src = ip4->src_addr().to_string();
|
||||||
|
dst = ip4->dst_addr().to_string();
|
||||||
|
|
||||||
|
if (auto *tcp = pdu.find_pdu<Tins::TCP>()) {
|
||||||
|
type = "TCP";
|
||||||
|
sport = tcp->sport();
|
||||||
|
dport = tcp->dport();
|
||||||
|
} else if (auto *udp = pdu.find_pdu<Tins::UDP>()) {
|
||||||
|
type = "UDP";
|
||||||
|
sport = udp->sport();
|
||||||
|
dport = udp->dport();
|
||||||
|
} else if (pdu.find_pdu<Tins::ICMP>()) {
|
||||||
|
type = "ICMP";
|
||||||
|
} else {
|
||||||
|
type = "IP";
|
||||||
|
}
|
||||||
|
} else if (auto *ip6 = pdu.find_pdu<Tins::IPv6>()) {
|
||||||
|
src = ip6->src_addr().to_string();
|
||||||
|
dst = ip6->dst_addr().to_string();
|
||||||
|
|
||||||
|
if (auto *tcp = pdu.find_pdu<Tins::TCP>()) {
|
||||||
|
type = "TCP";
|
||||||
|
sport = tcp->sport();
|
||||||
|
dport = tcp->dport();
|
||||||
|
} else if (auto *udp = pdu.find_pdu<Tins::UDP>()) {
|
||||||
|
type = "UDP";
|
||||||
|
sport = udp->sport();
|
||||||
|
dport = udp->dport();
|
||||||
|
} else if (pdu.find_pdu<Tins::ICMPv6>()) {
|
||||||
|
type = "ICMPv6";
|
||||||
|
} else {
|
||||||
|
type = "IPv6";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger::info("(unknown src) -> (unknown dst) [OTHER]");
|
||||||
|
return running();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask_) {
|
||||||
|
src = hideHalf(src);
|
||||||
|
dst = hideHalf(dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
printLine(src, dst, type, sport, dport);
|
||||||
|
return running();
|
||||||
|
};
|
||||||
|
|
||||||
|
sniffer.sniff_loop(handler);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sniffer::stop()
|
||||||
|
{
|
||||||
|
stop_.store(true, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Sniffer::hideHalf(const std::string &s) const
|
||||||
|
{
|
||||||
|
if (s.empty()) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t n = s.size();
|
||||||
|
const size_t hide = n / 2;
|
||||||
|
const size_t keep = n - hide;
|
||||||
|
std::string out = s.substr(0, keep);
|
||||||
|
out.append(hide, '*');
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Sniffer::resolveIfaceNameByIp(const std::string &ip)
|
||||||
|
{
|
||||||
|
const Tins::IPv4Address want(ip);
|
||||||
|
for (const auto &iface : Tins::NetworkInterface::all()) {
|
||||||
|
const auto info = iface.addresses(); // .ip_addr/.netmask/...
|
||||||
|
if (info.ip_addr == want) {
|
||||||
|
return iface.name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw std::runtime_error("Interface with IP " + ip + " not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sniffer::printLine(
|
||||||
|
const std::string &src,
|
||||||
|
const std::string &dst,
|
||||||
|
const char *type,
|
||||||
|
uint16_t sport,
|
||||||
|
uint16_t dport
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (sport || dport) {
|
||||||
|
Logger::info(
|
||||||
|
std::string(src) + ":" + std::to_string(sport) + " -> " + dst + ":"
|
||||||
|
+ std::to_string(dport) + " [" + type + "]"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Logger::info(std::string(src) + " -> " + dst + " [" + type + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Sniffer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Sniffer(std::string localIp, bool maskIp = false);
|
||||||
|
|
||||||
|
Sniffer(const Sniffer &) = delete;
|
||||||
|
Sniffer &operator=(const Sniffer &) = delete;
|
||||||
|
|
||||||
|
std::string hideHalf(const std::string &s) const;
|
||||||
|
|
||||||
|
int run();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::string resolveIfaceNameByIp(const std::string &ip);
|
||||||
|
static void printLine(
|
||||||
|
const std::string &src,
|
||||||
|
const std::string &dst,
|
||||||
|
const char *type,
|
||||||
|
uint16_t sport = 0,
|
||||||
|
uint16_t dport = 0
|
||||||
|
);
|
||||||
|
|
||||||
|
std::string ip_;
|
||||||
|
std::atomic<bool> stop_{false};
|
||||||
|
bool mask_{false};
|
||||||
|
};
|
||||||
+15
-27
@@ -1,38 +1,26 @@
|
|||||||
#include "cli_base.hpp"
|
|
||||||
#include "logger.hpp"
|
#include "logger.hpp"
|
||||||
#include "port_scanner.hpp"
|
#include "packet_sniffer_cli.hpp"
|
||||||
#include "portscan_cli.hpp"
|
#include "sniffer.hpp"
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
#include <string>
|
int main(int argc, char *argv[])
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
static std::vector<uint16_t> allPorts()
|
|
||||||
{
|
|
||||||
std::vector<uint16_t> v;
|
|
||||||
v.reserve(65535);
|
|
||||||
for (uint32_t p = 1; p <= 65535; ++p) {
|
|
||||||
v.push_back(static_cast<uint16_t>(p));
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
|
||||||
{
|
{
|
||||||
Logger::init(true);
|
Logger::init(true);
|
||||||
|
try {
|
||||||
PortScanCli cli;
|
PacketSnifferCli cli;
|
||||||
cli.parse(argc, argv);
|
cli.parse(argc, argv);
|
||||||
if (cli.isHelp()) {
|
if (cli.isHelp()) {
|
||||||
cli.printHelp();
|
cli.printHelp();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string ip = cli.getIp();
|
Sniffer sniffer(cli.getIp(), cli.isMaskEnabled());
|
||||||
auto ports = allPorts();
|
return sniffer.run();
|
||||||
|
} catch (const std::exception &ex) {
|
||||||
Logger::info("Starting full port scan (1..65535)...");
|
Logger::fatal(ex.what());
|
||||||
PortScanner scanner(ip, ports);
|
return 1;
|
||||||
scanner.run();
|
} catch (...) {
|
||||||
Logger::info("Scan completed.");
|
Logger::fatal("unknown error");
|
||||||
return 0;
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ add_requires("cmake::Boost", {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
target "port_scanner"
|
add_requires("cmake::libtins", { alias = "libtins", system = true })
|
||||||
|
|
||||||
|
target "packet_sniffer"
|
||||||
do
|
do
|
||||||
set_kind "binary"
|
set_kind "binary"
|
||||||
add_files("src/main.cpp", "src/cli/*.cpp", "src/common/*.cpp", "src/core/*.cpp")
|
add_files("src/main.cpp", "src/cli/*.cpp", "src/common/*.cpp", "src/core/*.cpp")
|
||||||
add_includedirs("src", "src/cli", "src/common", "src/core", { public = true })
|
add_includedirs("src", "src/cli", "src/common", "src/core", { public = true })
|
||||||
add_packages "boost"
|
add_packages("boost", "libtins")
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user