Skip to content
API Documentation
BastionGuard™
NFQueue.cpp
Go to the documentation of this file.
3
4#if defined(BSC_HAS_NFQ)
5#include <arpa/inet.h>
6#include <cerrno>
7#include <cstring>
8#include <linux/netfilter.h>
9#include <linux/netfilter/nfnetlink_queue.h>
10#include <linux/types.h>
11#include <libnetfilter_queue/libnetfilter_queue.h>
12#include <memory>
13#include <netinet/ip.h>
14#include <netinet/ip6.h>
15#include <netinet/tcp.h>
16#include <netinet/udp.h>
17#include <sys/socket.h>
18#include <thread>
19#include <unistd.h>
20
21namespace bsc::netfilter {
22namespace {
23constexpr int kAF_INET = 2;
24constexpr int kAF_INET6 = 10;
25
26struct ParsedPacket {
27 bsc::model::Connection conn;
28 bool ok{false};
29};
30
31std::string to_ipv4(std::uint32_t ip) {
32 in_addr a{};
33 a.s_addr = ip;
34 char buf[INET_ADDRSTRLEN]{};
35 inet_ntop(AF_INET, &a, buf, sizeof(buf));
36 return buf;
37}
38
39std::string to_ipv6(const in6_addr& addr) {
40 char buf[INET6_ADDRSTRLEN]{};
41 inet_ntop(AF_INET6, &addr, buf, sizeof(buf));
42 return buf;
43}
44
45ParsedPacket parse_payload(unsigned char* data, int len) {
46 ParsedPacket out;
47 if (!data || len < 1) return out;
48
49 const auto version = static_cast<std::uint8_t>(data[0] >> 4);
50 if (version == 4) {
51 if (len < static_cast<int>(sizeof(iphdr))) return out;
52 const auto* ip = reinterpret_cast<const iphdr*>(data);
53 const auto ihl = static_cast<int>(ip->ihl) * 4;
54 if (len < ihl) return out;
55 out.conn.src_ip = to_ipv4(ip->saddr);
56 out.conn.dst_ip = to_ipv4(ip->daddr);
57 if (ip->protocol == IPPROTO_TCP) {
58 if (len < ihl + static_cast<int>(sizeof(tcphdr))) return out;
59 const auto* tcp = reinterpret_cast<const tcphdr*>(data + ihl);
60 out.conn.protocol = "tcp";
61 out.conn.src_port = ntohs(tcp->source);
62 out.conn.dst_port = ntohs(tcp->dest);
63 } else if (ip->protocol == IPPROTO_UDP) {
64 if (len < ihl + static_cast<int>(sizeof(udphdr))) return out;
65 const auto* udp = reinterpret_cast<const udphdr*>(data + ihl);
66 out.conn.protocol = "udp";
67 out.conn.src_port = ntohs(udp->source);
68 out.conn.dst_port = ntohs(udp->dest);
69 } else {
70 return out;
71 }
72 out.ok = true;
73 return out;
74 }
75
76 if (version == 6) {
77 if (len < static_cast<int>(sizeof(ip6_hdr))) return out;
78 const auto* ip6 = reinterpret_cast<const ip6_hdr*>(data);
79 out.conn.src_ip = to_ipv6(ip6->ip6_src);
80 out.conn.dst_ip = to_ipv6(ip6->ip6_dst);
81 const auto nexthdr = ip6->ip6_nxt;
82 const auto offset = static_cast<int>(sizeof(ip6_hdr));
83 if (nexthdr == IPPROTO_TCP) {
84 if (len < offset + static_cast<int>(sizeof(tcphdr))) return out;
85 const auto* tcp = reinterpret_cast<const tcphdr*>(data + offset);
86 out.conn.protocol = "tcp6";
87 out.conn.src_port = ntohs(tcp->source);
88 out.conn.dst_port = ntohs(tcp->dest);
89 } else if (nexthdr == IPPROTO_UDP) {
90 if (len < offset + static_cast<int>(sizeof(udphdr))) return out;
91 const auto* udp = reinterpret_cast<const udphdr*>(data + offset);
92 out.conn.protocol = "udp6";
93 out.conn.src_port = ntohs(udp->source);
94 out.conn.dst_port = ntohs(udp->dest);
95 } else {
96 return out;
97 }
98 out.ok = true;
99 }
100 return out;
101}
102
103class QueueOwner;
104static int nfq_callback(nfq_q_handle* qh, nfgenmsg*, nfq_data* nfa, void* data);
105
106class QueueOwner {
107 public:
108 std::uint16_t queue_id{};
109 NFQueue::Handler handler;
110 nfq_handle* handle{nullptr};
111 nfq_q_handle* queue{nullptr};
112 int fd{-1};
113 std::thread thread;
114 std::atomic_bool running{false};
115
116 ~QueueOwner() { stop(); }
117
118 bool start() {
119 handle = nfq_open();
120 if (!handle) return false;
121 if (nfq_unbind_pf(handle, kAF_INET) < 0 && errno != ENOENT) {
122 bsc::logging::Logger::instance().warning("nfq_unbind_pf(AF_INET) failed");
123 }
124 if (nfq_unbind_pf(handle, kAF_INET6) < 0 && errno != ENOENT) {
125 bsc::logging::Logger::instance().warning("nfq_unbind_pf(AF_INET6) failed");
126 }
127 if (nfq_bind_pf(handle, kAF_INET) < 0 || nfq_bind_pf(handle, kAF_INET6) < 0) {
128 stop();
129 return false;
130 }
131 queue = nfq_create_queue(handle, queue_id, &nfq_callback, this);
132 if (!queue) {
133 stop();
134 return false;
135 }
136 if (nfq_set_mode(queue, NFQNL_COPY_PACKET, 0xffff) < 0) {
137 stop();
138 return false;
139 }
140 nfq_set_queue_maxlen(queue, 4096);
141#ifdef NFQA_CFG_F_UID_GID
142 nfq_set_queue_flags(queue, NFQA_CFG_F_UID_GID, NFQA_CFG_F_UID_GID);
143#endif
144 fd = nfq_fd(handle);
145 running = true;
146 thread = std::thread([this]() {
147 char buf[8192] __attribute__((aligned));
148 while (running) {
149 const int rv = recv(fd, buf, sizeof(buf), 0);
150 if (rv >= 0) {
151 nfq_handle_packet(handle, buf, rv);
152 continue;
153 }
154 if (errno == EINTR) continue;
155 if (running) {
156 bsc::logging::Logger::instance().warning(std::string("nfqueue recv failed: ") + std::strerror(errno));
157 }
158 break;
159 }
160 });
161 return true;
162 }
163
164 void stop() {
165 running = false;
166 if (fd >= 0) {
167 ::shutdown(fd, SHUT_RDWR);
168 ::close(fd);
169 fd = -1;
170 }
171 if (thread.joinable()) thread.join();
172 if (queue) {
173 nfq_destroy_queue(queue);
174 queue = nullptr;
175 }
176 if (handle) {
177 nfq_close(handle);
178 handle = nullptr;
179 }
180 }
181};
182
183static int nfq_callback(nfq_q_handle* qh, nfgenmsg*, nfq_data* nfa, void* data) {
184 auto* owner = reinterpret_cast<QueueOwner*>(data);
185 if (!owner) return 0;
186
187 std::uint32_t id = 0;
188 if (auto* ph = nfq_get_msg_packet_hdr(nfa)) id = ntohl(ph->packet_id);
189
190 unsigned char* payload = nullptr;
191 const int payload_len = nfq_get_payload(nfa, &payload);
192 auto parsed = parse_payload(payload, payload_len);
193 PacketInfo info;
194 info.id = id;
195 info.queue_id = owner->queue_id;
196 info.mark = nfq_get_nfmark(nfa);
197 info.in_dev = nfq_get_indev(nfa);
198 info.out_dev = nfq_get_outdev(nfa);
199#ifdef NFQA_CFG_F_UID_GID
200 std::uint32_t uid = 0xffffffff;
201 if (nfq_get_uid(nfa, &uid) >= 0) info.uid = uid;
202#endif
203 if (parsed.ok) info.connection = std::move(parsed.conn);
204 // Attenzione: user_id=0 e' un UID valido (root). Usiamo il flag letto da
205 // nfq_get_uid (info.uid e' 0xffffffff quando non disponibile).
206 if (info.uid != 0xffffffff && info.connection.user_id == 0xffffffff) info.connection.user_id = info.uid;
207
208 Decision decision;
209 if (owner->handler) decision = owner->handler(info);
210 return nfq_set_verdict2(qh, id, static_cast<std::uint32_t>(decision.verdict), decision.set_mark ? decision.mark : 0, 0, nullptr);
211}
212} // namespace
213
214class NFQueue::Impl {
215 public:
216 std::unique_ptr<QueueOwner> owner;
217};
218
219NFQueue::NFQueue() : impl_(std::make_unique<Impl>()) {}
220NFQueue::~NFQueue() { close(); }
221
222bool NFQueue::open(std::uint16_t queue_id, Handler handler) {
223 close();
224 impl_->owner = std::make_unique<QueueOwner>();
225 impl_->owner->queue_id = queue_id;
226 impl_->owner->handler = std::move(handler);
227 running_ = impl_->owner->start();
228 return running_;
229}
230
231void NFQueue::close() {
232 running_ = false;
233 if (impl_ && impl_->owner) {
234 impl_->owner->stop();
235 impl_->owner.reset();
236 }
237}
238
239bool available() { return true; }
240
241} // namespace bsc::netfilter
242
243#else
244
245namespace bsc::netfilter {
247NFQueue::NFQueue() : impl_(std::make_unique<Impl>()) {}
248NFQueue::~NFQueue() = default;
249bool NFQueue::open(std::uint16_t, Handler) { return false; }
251bool available() { return false; }
252} // namespace bsc::netfilter
253
254#endif
asio::ip::tcp tcp
#define IPPROTO_TCP
Definition bsc_ebpf.c:60
#define AF_INET
Definition bsc_ebpf.c:56
#define AF_INET6
Definition bsc_ebpf.c:57
#define IPPROTO_UDP
Definition bsc_ebpf.c:61
void warning(const std::string &msg)
Definition Logger.cpp:22
static Logger & instance()
Definition Logger.cpp:11
bool open(std::uint16_t queue_id, Handler handler)
Definition NFQueue.cpp:249
std::unique_ptr< Impl > impl_
Definition NFQueue.hpp:48
std::function< Decision(const PacketInfo &)> Handler
Definition NFQueue.hpp:37
std::map< std::string, std::string > parse_payload(const std::string &payload)
bool available()
Definition NFQueue.cpp:251