Skip to content
API Documentation
BastionGuard™
SockDiag.cpp
Go to the documentation of this file.
1/*
2 * BastionGuard™ Secure Connection
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 * GPL-3.0
5 */
6
8
9#include <arpa/inet.h>
10#include <cstring>
11#include <errno.h>
12#include <linux/inet_diag.h>
13#include <linux/netlink.h>
14#include <linux/sock_diag.h>
15#include <netinet/in.h>
16#include <sys/socket.h>
17#include <unistd.h>
18
19namespace bsc::net {
20
21namespace {
22
23// Query a NETLINK_SOCK_DIAG per TCP o UDP su IPv4 o IPv6 con filtro esatto
24// sulla 4-tupla. Ritorna inode+uid del socket trovato, o nullopt.
25std::optional<SockInfo> sockdiag_query(int family, int protocol,
26 const std::string& src_ip, std::uint16_t src_port,
27 const std::string& dst_ip, std::uint16_t dst_port)
28{
29 int fd = ::socket(AF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_SOCK_DIAG);
30 if (fd < 0) return std::nullopt;
31
32 // Timeout 100ms sulla ricezione: non dobbiamo mai bloccare NFQUEUE
33 // per tempi lunghi. Il kernel risponde in microsecondi quando trova.
34 struct timeval tv{};
35 tv.tv_sec = 0;
36 tv.tv_usec = 100 * 1000;
37 ::setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
38
39 // Prepara la richiesta inet_diag con filtro esatto sulla 4-tupla.
40 struct {
41 struct nlmsghdr nlh;
42 struct inet_diag_req_v2 req;
43 } msg{};
44
45 msg.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(msg.req));
46 msg.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY;
47 msg.nlh.nlmsg_flags = NLM_F_REQUEST;
48 msg.nlh.nlmsg_seq = 1;
49
50 msg.req.sdiag_family = static_cast<std::uint8_t>(family);
51 msg.req.sdiag_protocol = static_cast<std::uint8_t>(protocol);
52 // Stati TCP di interesse: tutti per UDP (che non ha stati).
53 // Per TCP accettiamo anche SYN_SENT (3) perche' NFQUEUE ci consegna il
54 // SYN prima che la connessione sia stabilita. Bitmask completa per
55 // coprire tutti i casi utili.
56 msg.req.idiag_states = 0xFFFFFFFFu;
57 msg.req.idiag_ext = 0;
58
59 // Popola la 4-tupla nel filtro. Attenzione: saddr/daddr sono in
60 // network-byte-order; porte idem.
61 msg.req.id.idiag_sport = htons(src_port);
62 msg.req.id.idiag_dport = htons(dst_port);
63 msg.req.id.idiag_if = 0;
64 msg.req.id.idiag_cookie[0] = INET_DIAG_NOCOOKIE;
65 msg.req.id.idiag_cookie[1] = INET_DIAG_NOCOOKIE;
66
67 if (family == AF_INET) {
68 in_addr sa{}, da{};
69 if (inet_pton(AF_INET, src_ip.c_str(), &sa) != 1 ||
70 inet_pton(AF_INET, dst_ip.c_str(), &da) != 1) {
71 ::close(fd); return std::nullopt;
72 }
73 std::memcpy(msg.req.id.idiag_src, &sa.s_addr, 4);
74 std::memcpy(msg.req.id.idiag_dst, &da.s_addr, 4);
75 } else {
76 in6_addr sa{}, da{};
77 if (inet_pton(AF_INET6, src_ip.c_str(), &sa) != 1 ||
78 inet_pton(AF_INET6, dst_ip.c_str(), &da) != 1) {
79 ::close(fd); return std::nullopt;
80 }
81 std::memcpy(msg.req.id.idiag_src, &sa.s6_addr, 16);
82 std::memcpy(msg.req.id.idiag_dst, &da.s6_addr, 16);
83 }
84
85 // Invia al kernel (dest: kernel = pid 0)
86 struct sockaddr_nl dest{};
87 dest.nl_family = AF_NETLINK;
88 const auto sent = ::sendto(fd, &msg, msg.nlh.nlmsg_len, 0,
89 reinterpret_cast<struct sockaddr*>(&dest), sizeof(dest));
90 if (sent < 0) { ::close(fd); return std::nullopt; }
91
92 // Ricevi la risposta
93 char buf[8192];
94 const auto rcv = ::recv(fd, buf, sizeof(buf), 0);
95 ::close(fd);
96 if (rcv <= 0) return std::nullopt;
97 // NLMSG_NEXT decrementa in-place il contatore, quindi n non puo'
98 // essere const.
99 auto n = static_cast<unsigned int>(rcv);
100
101 struct nlmsghdr* nh = reinterpret_cast<struct nlmsghdr*>(buf);
102 for (; NLMSG_OK(nh, n); nh = NLMSG_NEXT(nh, n)) {
103 if (nh->nlmsg_type == NLMSG_DONE) break;
104 if (nh->nlmsg_type == NLMSG_ERROR) return std::nullopt;
105 if (nh->nlmsg_type != SOCK_DIAG_BY_FAMILY) continue;
106
107 auto* diag = reinterpret_cast<struct inet_diag_msg*>(NLMSG_DATA(nh));
108 SockInfo info;
109 info.inode = diag->idiag_inode;
110 info.uid = diag->idiag_uid;
111 if (info.inode != 0) return info;
112 }
113 return std::nullopt;
114}
115
116} // namespace
117
118std::optional<SockInfo> sockdiag_lookup_tcp(const std::string& src_ip,
119 std::uint16_t src_port,
120 const std::string& dst_ip,
121 std::uint16_t dst_port)
122{
123 if (src_ip.empty() || dst_ip.empty()) return std::nullopt;
124 const bool is_v6 = src_ip.find(':') != std::string::npos ||
125 dst_ip.find(':') != std::string::npos;
126 return sockdiag_query(is_v6 ? AF_INET6 : AF_INET, IPPROTO_TCP,
127 src_ip, src_port, dst_ip, dst_port);
128}
129
130std::optional<SockInfo> sockdiag_lookup_udp(const std::string& src_ip,
131 std::uint16_t src_port,
132 const std::string& dst_ip,
133 std::uint16_t dst_port)
134{
135 if (src_ip.empty() || dst_ip.empty()) return std::nullopt;
136 const bool is_v6 = src_ip.find(':') != std::string::npos ||
137 dst_ip.find(':') != std::string::npos;
138 return sockdiag_query(is_v6 ? AF_INET6 : AF_INET, IPPROTO_UDP,
139 src_ip, src_port, dst_ip, dst_port);
140}
141
142} // namespace bsc::net
#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
std::optional< SockInfo > sockdiag_query(int family, int protocol, const std::string &src_ip, std::uint16_t src_port, const std::string &dst_ip, std::uint16_t dst_port)
Definition SockDiag.cpp:25
std::optional< SockInfo > sockdiag_lookup_tcp(const std::string &src_ip, std::uint16_t src_port, const std::string &dst_ip, std::uint16_t dst_port)
Definition SockDiag.cpp:118
std::optional< SockInfo > sockdiag_lookup_udp(const std::string &src_ip, std::uint16_t src_port, const std::string &dst_ip, std::uint16_t dst_port)
Definition SockDiag.cpp:130
std::uint32_t uid
Definition SockDiag.hpp:35
std::uint32_t inode
Definition SockDiag.hpp:34