| 87 | namespace { |
| 88 | |
| 89 | ByteBuf format_socket_csv(const Engine& eng, SocketInfo::Proto only, |
| 90 | const char* proto_name) |
| 91 | { |
| 92 | auto socks = (only == SocketInfo::P_UDP) |
| 93 | ? enumerate_udp_sockets(eng) |
| 94 | : enumerate_tcp_sockets(eng); |
| 95 | std::string out; |
| 96 | out.reserve(8 * 1024); |
| 97 | // Emit the header even when empty so SIEM ingesters that schema- |
| 98 | // detect from the first row don't blow up. Append a comment line |
| 99 | // explaining the empty result (CSV technically allows #-prefix |
| 100 | // lines; jq/pandas typically skip rows starting with #). |
| 101 | out += "proto,state,family,local_ip,local_port,remote_ip,remote_port,sock_va\r\n"; |
| 102 | if (socks.empty()) { |
| 103 | out += "# (no sockets enumerated — likely missing tcp_hashinfo / " |
| 104 | "udp_table symbol in the dump's ISF)\r\n"; |
| 105 | return ByteBuf(out.begin(), out.end()); |
| 106 | } |
| 107 | for (const auto& s : socks) { |
| 108 | if (s.proto != only) continue; |
| 109 | const char* fam = s.family == SocketInfo::AF_INET6 ? "INET6" |
| 110 | : s.family == SocketInfo::AF_INET4 ? "INET" : "?"; |
| 111 | out += fmt::format("{},{},{},{},{},{},{},{:#x}\r\n", |
| 112 | proto_name, |
| 113 | tcp_state_name(s.state), |
| 114 | fam, |
| 115 | csv_quote(fmt_addr(s, s.local_addr)), s.local_port, |
| 116 | csv_quote(fmt_addr(s, s.remote_addr)), s.remote_port, |
| 117 | s.sock_va); |
| 118 | } |
| 119 | return ByteBuf(out.begin(), out.end()); |
| 120 | } |
| 121 | |
| 122 | } // anonymous |
| 123 |
no test coverage detected