ebpf-blocker 1.0.0
XDP-based packet blocker using eBPF
Loading...
Searching...
No Matches
db.cpp
Go to the documentation of this file.
1/// \file db.cpp
2/// \brief Реализация обёртки SQLite-базы данных и утилит CIDR.
3/// \details Содержит реализацию BlockerDB, парсера CIDR и вспомогательных
4/// функций для работы с IP-адресами.
5
6#include "db.h"
7
8#include <arpa/inet.h>
9#include <cstring>
10#include <iostream>
11#include <sstream>
12#include <stdexcept>
13
14/**********************************************************************/
15/* CIDR parsing */
16/**********************************************************************/
17
18CIDR ParseCIDR(const std::string &str) {
19 CIDR result{};
20 result.prefixlen = 32;
21
22 auto slash = str.find('/');
23 std::string ip_str =
24 (slash != std::string::npos) ? str.substr(0, slash) : str;
25
26 if (inet_pton(AF_INET, ip_str.c_str(), &result.ip) != 1)
27 throw std::invalid_argument("invalid IP address: " + ip_str);
28
29 if (slash != std::string::npos) {
30 unsigned long pl = std::stoul(str.substr(slash + 1));
31 if (pl > 32)
32 throw std::invalid_argument("invalid prefix length: " +
33 std::to_string(pl));
34 result.prefixlen = static_cast<uint32_t>(pl);
35 result.ip &= htonl(pl == 0 ? 0 : (0xFFFFFFFFu << (32 - pl)));
36 }
37
38 return result;
39}
40
41/**********************************************************************/
42/* IP conversion */
43/**********************************************************************/
44
45std::string IPToString(uint32_t ip) {
46 char buf[INET_ADDRSTRLEN];
47 inet_ntop(AF_INET, &ip, buf, sizeof(buf));
48 return buf;
49}
50
51/**********************************************************************/
52/* SQLite callback */
53/**********************************************************************/
54
55/// Колбэк sqlite3_exec, собирающий строки (ip, comment).
56/// @param[in] data Указатель на вектор BlockEntry.
57/// @param[in] argv Массив полей строки SQL.
58/// @return 0 (код успеха для sqlite3_exec).
59static int StepCallback(void *data, int /*argc*/, char **argv,
60 char ** /*colNames*/) {
61 auto *entries = static_cast<std::vector<BlockEntry> *>(data);
62 if (argv[0] && argv[1])
63 entries->push_back({argv[0], argv[1]});
64 return 0;
65}
66
67/**********************************************************************/
68/* BlockerDB constructor / destructor */
69/**********************************************************************/
70
71BlockerDB::BlockerDB(const std::string &path) : db_(nullptr) {
72 int rc = sqlite3_open(path.c_str(), &db_);
73 if (rc != SQLITE_OK) {
74 std::string err = sqlite3_errmsg(db_);
75 sqlite3_close(db_);
76 throw std::runtime_error("failed to open database: " + err);
77 }
78}
79
81 if (db_)
82 sqlite3_close(db_);
83}
84
85/**********************************************************************/
86/* Schema migration */
87/**********************************************************************/
88
90 sqlite3_exec(db_, "PRAGMA journal_mode=WAL;", nullptr, nullptr, nullptr);
91
92 const char *sql = "CREATE TABLE IF NOT EXISTS whitelist_ips ("
93 " id INTEGER PRIMARY KEY AUTOINCREMENT,"
94 " ip TEXT NOT NULL UNIQUE,"
95 " comment TEXT DEFAULT '',"
96 " created_at TEXT DEFAULT (datetime('now'))"
97 ");"
98 "CREATE TABLE IF NOT EXISTS blocked_ips ("
99 " id INTEGER PRIMARY KEY AUTOINCREMENT,"
100 " ip TEXT NOT NULL UNIQUE,"
101 " comment TEXT DEFAULT '',"
102 " created_at TEXT DEFAULT (datetime('now'))"
103 ");";
104
105 char *err = nullptr;
106 if (sqlite3_exec(db_, sql, nullptr, nullptr, &err) != SQLITE_OK) {
107 std::string msg = err;
108 sqlite3_free(err);
109 throw std::runtime_error("migration failed: " + msg);
110 }
111
112 /* Idempotent: silently ignored if column already exists. */
113 sqlite3_exec(db_, "ALTER TABLE blocked_ips ADD COLUMN last_blocked_at TEXT",
114 nullptr, nullptr, nullptr);
115}
116
117/**********************************************************************/
118/* CIDR match helper */
119/**********************************************************************/
120
121/// Проверяет, входит ли IP в указанную CIDR-подсеть.
122/// @param[in] ip IP-адрес в network byte order.
123/// @param[in] cidr_str CIDR-строка вида "10.0.0.0/8".
124/// @return true если ip находится в подсети.
125static bool MatchesCIDR(uint32_t ip, const std::string &cidr_str) {
126 auto slash = cidr_str.find('/');
127 if (slash == std::string::npos)
128 return false;
129
130 uint32_t cidr_ip;
131 if (inet_pton(AF_INET, cidr_str.substr(0, slash).c_str(), &cidr_ip) != 1)
132 return false;
133
134 unsigned long pl = std::stoul(cidr_str.substr(slash + 1));
135 if (pl > 32)
136 return false;
137
138 uint32_t mask = htonl(pl == 0 ? 0 : (0xFFFFFFFFu << (32 - pl)));
139 return (ip & mask) == (cidr_ip & mask);
140}
141
142/**********************************************************************/
143/* Update last_blocked_at */
144/**********************************************************************/
145
147 std::string ip_str = IPToString(ip);
148
149 /* Exact match */
150 {
151 const std::string sql =
152 "UPDATE blocked_ips SET last_blocked_at = datetime('now') WHERE ip = ?";
153 sqlite3_stmt *stmt = nullptr;
154 if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) == SQLITE_OK) {
155 sqlite3_bind_text(stmt, 1, ip_str.c_str(), -1, SQLITE_TRANSIENT);
156 sqlite3_step(stmt);
157 sqlite3_finalize(stmt);
158 }
159 }
160
161 /* CIDR match: update any covering subnet entry */
162 {
163 const std::string sql = "SELECT ip FROM blocked_ips WHERE ip LIKE '%/%'";
164 sqlite3_stmt *stmt = nullptr;
165 if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK)
166 return;
167
168 std::vector<std::string> matching;
169 while (sqlite3_step(stmt) == SQLITE_ROW) {
170 const char *cidr_str =
171 reinterpret_cast<const char *>(sqlite3_column_text(stmt, 0));
172 if (cidr_str && MatchesCIDR(ip, cidr_str))
173 matching.emplace_back(cidr_str);
174 }
175 sqlite3_finalize(stmt);
176
177 for (const auto &cidr : matching) {
178 const std::string upd = "UPDATE blocked_ips SET last_blocked_at = "
179 "datetime('now') WHERE ip = ?";
180 sqlite3_stmt *us = nullptr;
181 if (sqlite3_prepare_v2(db_, upd.c_str(), -1, &us, nullptr) == SQLITE_OK) {
182 sqlite3_bind_text(us, 1, cidr.c_str(), -1, SQLITE_TRANSIENT);
183 sqlite3_step(us);
184 sqlite3_finalize(us);
185 }
186 }
187 }
188}
189
190/**********************************************************************/
191/* Query helpers */
192/**********************************************************************/
193
194std::vector<BlockEntry> BlockerDB::GetWhitelist() {
195 return GetEntries("whitelist_ips");
196}
197
198std::vector<BlockEntry> BlockerDB::GetBlacklist() {
199 return GetEntries("blocked_ips");
200}
201
202std::vector<BlockEntry> BlockerDB::GetEntries(const std::string &table) {
203 std::vector<BlockEntry> entries;
204 std::string sql = "SELECT ip, comment FROM " + table;
205
206 char *err = nullptr;
207 if (sqlite3_exec(db_, sql.c_str(), StepCallback, &entries, &err) !=
208 SQLITE_OK) {
209 std::string msg = err;
210 sqlite3_free(err);
211 throw std::runtime_error("query failed: " + msg);
212 }
213
214 return entries;
215}
std::vector< BlockEntry > GetBlacklist()
Definition db.cpp:198
void Migrate()
Definition db.cpp:89
BlockerDB(const std::string &path)
Definition db.cpp:71
sqlite3 * db_
Definition db.h:74
void UpdateLastBlockedAt(uint32_t ip)
Definition db.cpp:146
std::vector< BlockEntry > GetWhitelist()
Definition db.cpp:194
std::vector< BlockEntry > GetEntries(const std::string &table)
Definition db.cpp:202
~BlockerDB()
Definition db.cpp:80
std::string IPToString(uint32_t ip)
Definition db.cpp:45
static int StepCallback(void *data, int, char **argv, char **)
Definition db.cpp:59
static bool MatchesCIDR(uint32_t ip, const std::string &cidr_str)
Definition db.cpp:125
CIDR ParseCIDR(const std::string &str)
Definition db.cpp:18
Обёртка SQLite-базы данных и утилиты парсинга CIDR.
std::string IPToString(uint32_t ip)
Definition db.cpp:45
Распарсенное представление CIDR-подсети.
Definition db.h:21
uint32_t prefixlen
Длина префикса (0-32). 32 означает одиночный хост.
Definition db.h:23