You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

434 lines
10 KiB

  1. From 9d9ea2cd70a369a7f665a322e6c53631e01a2570 Mon Sep 17 00:00:00 2001
  2. From: Andreas Jaggi <andreas.jaggi@waterwave.ch>
  3. Date: Wed, 30 May 2018 22:15:36 +0200
  4. Subject: ulogd: json: send messages to a remote host / unix socket
  5. Extend the JSON output plugin so that the generated JSON stream can be
  6. sent to a remote host via TCP/UDP or to a local unix socket.
  7. Signed-off-by: Andreas Jaggi <andreas.jaggi@waterwave.ch>
  8. Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
  9. ---
  10. output/ulogd_output_JSON.c | 291 +++++++++++++++++++++++++++++++++++++++++----
  11. ulogd.conf.in | 11 ++
  12. 2 files changed, 281 insertions(+), 21 deletions(-)
  13. --- a/output/ulogd_output_JSON.c
  14. +++ b/output/ulogd_output_JSON.c
  15. @@ -20,10 +20,15 @@
  16. #include <stdio.h>
  17. #include <stdlib.h>
  18. +#include <unistd.h>
  19. #include <string.h>
  20. #include <time.h>
  21. #include <errno.h>
  22. #include <inttypes.h>
  23. +#include <sys/types.h>
  24. +#include <sys/socket.h>
  25. +#include <sys/un.h>
  26. +#include <netdb.h>
  27. #include <ulogd/ulogd.h>
  28. #include <ulogd/conffile.h>
  29. #include <jansson.h>
  30. @@ -36,6 +41,10 @@
  31. #define ULOGD_JSON_DEFAULT_DEVICE "Netfilter"
  32. #endif
  33. +#define host_ce(x) (x->ces[JSON_CONF_HOST])
  34. +#define port_ce(x) (x->ces[JSON_CONF_PORT])
  35. +#define mode_ce(x) (x->ces[JSON_CONF_MODE])
  36. +#define file_ce(x) (x->ces[JSON_CONF_FILENAME])
  37. #define unlikely(x) __builtin_expect((x),0)
  38. struct json_priv {
  39. @@ -44,6 +53,15 @@ struct json_priv {
  40. int usec_idx;
  41. long cached_gmtoff;
  42. char cached_tz[6]; /* eg +0200 */
  43. + int mode;
  44. + int sock;
  45. +};
  46. +
  47. +enum json_mode {
  48. + JSON_MODE_FILE = 0,
  49. + JSON_MODE_TCP,
  50. + JSON_MODE_UDP,
  51. + JSON_MODE_UNIX
  52. };
  53. enum json_conf {
  54. @@ -53,6 +71,9 @@ enum json_conf {
  55. JSON_CONF_EVENTV1,
  56. JSON_CONF_DEVICE,
  57. JSON_CONF_BOOLEAN_LABEL,
  58. + JSON_CONF_MODE,
  59. + JSON_CONF_HOST,
  60. + JSON_CONF_PORT,
  61. JSON_CONF_MAX
  62. };
  63. @@ -95,15 +116,167 @@ static struct config_keyset json_kset =
  64. .options = CONFIG_OPT_NONE,
  65. .u = { .value = 0 },
  66. },
  67. + [JSON_CONF_MODE] = {
  68. + .key = "mode",
  69. + .type = CONFIG_TYPE_STRING,
  70. + .options = CONFIG_OPT_NONE,
  71. + .u = { .string = "file" },
  72. + },
  73. + [JSON_CONF_HOST] = {
  74. + .key = "host",
  75. + .type = CONFIG_TYPE_STRING,
  76. + .options = CONFIG_OPT_NONE,
  77. + .u = { .string = "127.0.0.1" },
  78. + },
  79. + [JSON_CONF_PORT] = {
  80. + .key = "port",
  81. + .type = CONFIG_TYPE_STRING,
  82. + .options = CONFIG_OPT_NONE,
  83. + .u = { .string = "12345" },
  84. + },
  85. },
  86. };
  87. +static void close_socket(struct json_priv *op) {
  88. + if (op->sock != -1) {
  89. + close(op->sock);
  90. + op->sock = -1;
  91. + }
  92. +}
  93. +
  94. +static int _connect_socket_unix(struct ulogd_pluginstance *pi)
  95. +{
  96. + struct json_priv *op = (struct json_priv *) &pi->private;
  97. + struct sockaddr_un u_addr;
  98. + int sfd;
  99. +
  100. + close_socket(op);
  101. +
  102. + ulogd_log(ULOGD_DEBUG, "connecting to unix:%s\n",
  103. + file_ce(pi->config_kset).u.string);
  104. +
  105. + sfd = socket(AF_UNIX, SOCK_STREAM, 0);
  106. + if (sfd == -1) {
  107. + return -1;
  108. + }
  109. + u_addr.sun_family = AF_UNIX;
  110. + strncpy(u_addr.sun_path, file_ce(pi->config_kset).u.string,
  111. + sizeof(u_addr.sun_path) - 1);
  112. + if (connect(sfd, (struct sockaddr *) &u_addr, sizeof(struct sockaddr_un)) == -1) {
  113. + close(sfd);
  114. + return -1;
  115. + }
  116. +
  117. + op->sock = sfd;
  118. +
  119. + return 0;
  120. +}
  121. +
  122. +static int _connect_socket_net(struct ulogd_pluginstance *pi)
  123. +{
  124. + struct json_priv *op = (struct json_priv *) &pi->private;
  125. + struct addrinfo hints;
  126. + struct addrinfo *result, *rp;
  127. + int sfd, s;
  128. +
  129. + close_socket(op);
  130. +
  131. + ulogd_log(ULOGD_DEBUG, "connecting to %s:%s\n",
  132. + host_ce(pi->config_kset).u.string,
  133. + port_ce(pi->config_kset).u.string);
  134. +
  135. + memset(&hints, 0, sizeof(struct addrinfo));
  136. + hints.ai_family = AF_UNSPEC;
  137. + hints.ai_socktype = op->mode == JSON_MODE_UDP ? SOCK_DGRAM : SOCK_STREAM;
  138. + hints.ai_protocol = 0;
  139. + hints.ai_flags = 0;
  140. +
  141. + s = getaddrinfo(host_ce(pi->config_kset).u.string,
  142. + port_ce(pi->config_kset).u.string, &hints, &result);
  143. + if (s != 0) {
  144. + ulogd_log(ULOGD_ERROR, "getaddrinfo: %s\n", gai_strerror(s));
  145. + return -1;
  146. + }
  147. +
  148. + for (rp = result; rp != NULL; rp = rp->ai_next) {
  149. + int on = 1;
  150. +
  151. + sfd = socket(rp->ai_family, rp->ai_socktype,
  152. + rp->ai_protocol);
  153. + if (sfd == -1)
  154. + continue;
  155. +
  156. + setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
  157. + (char *) &on, sizeof(on));
  158. +
  159. + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1)
  160. + break;
  161. +
  162. + close(sfd);
  163. + }
  164. +
  165. + freeaddrinfo(result);
  166. +
  167. + if (rp == NULL) {
  168. + return -1;
  169. + }
  170. +
  171. + op->sock = sfd;
  172. +
  173. + return 0;
  174. +}
  175. +
  176. +static int _connect_socket(struct ulogd_pluginstance *pi)
  177. +{
  178. + struct json_priv *op = (struct json_priv *) &pi->private;
  179. +
  180. + if (op->mode == JSON_MODE_UNIX)
  181. + return _connect_socket_unix(pi);
  182. + else
  183. + return _connect_socket_net(pi);
  184. +}
  185. +
  186. +static int json_interp_socket(struct ulogd_pluginstance *upi, char *buf, int buflen)
  187. +{
  188. + struct json_priv *opi = (struct json_priv *) &upi->private;
  189. + int ret = 0;
  190. +
  191. + if (opi->sock != -1)
  192. + ret = send(opi->sock, buf, buflen, MSG_NOSIGNAL);
  193. + free(buf);
  194. + if (ret != buflen) {
  195. + ulogd_log(ULOGD_ERROR, "Failure sending message: %s\n",
  196. + strerror(errno));
  197. + if (ret == -1 || opi->sock == -1)
  198. + return _connect_socket(upi);
  199. + else
  200. + return ULOGD_IRET_ERR;
  201. + }
  202. +
  203. + return ULOGD_IRET_OK;
  204. +}
  205. +
  206. +static int json_interp_file(struct ulogd_pluginstance *upi, char *buf)
  207. +{
  208. + struct json_priv *opi = (struct json_priv *) &upi->private;
  209. +
  210. + fprintf(opi->of, "%s", buf);
  211. + free(buf);
  212. +
  213. + if (upi->config_kset->ces[JSON_CONF_SYNC].u.value != 0)
  214. + fflush(opi->of);
  215. +
  216. + return ULOGD_IRET_OK;
  217. +}
  218. +
  219. #define MAX_LOCAL_TIME_STRING 38
  220. static int json_interp(struct ulogd_pluginstance *upi)
  221. {
  222. struct json_priv *opi = (struct json_priv *) &upi->private;
  223. unsigned int i;
  224. + char *buf;
  225. + int buflen;
  226. json_t *msg;
  227. msg = json_object();
  228. @@ -218,34 +391,65 @@ static int json_interp(struct ulogd_plug
  229. }
  230. }
  231. - json_dumpf(msg, opi->of, 0);
  232. - fprintf(opi->of, "\n");
  233. + buf = json_dumps(msg, 0);
  234. json_decref(msg);
  235. + if (buf == NULL) {
  236. + ulogd_log(ULOGD_ERROR, "Could not create message\n");
  237. + return ULOGD_IRET_ERR;
  238. + }
  239. + buflen = strlen(buf);
  240. + buf = realloc(buf, sizeof(char)*(buflen+2));
  241. + if (buf == NULL) {
  242. + ulogd_log(ULOGD_ERROR, "Could not create message\n");
  243. + return ULOGD_IRET_ERR;
  244. + }
  245. + strncat(buf, "\n", 1);
  246. + buflen++;
  247. - if (upi->config_kset->ces[JSON_CONF_SYNC].u.value != 0)
  248. - fflush(opi->of);
  249. + if (opi->mode == JSON_MODE_FILE)
  250. + return json_interp_file(upi, buf);
  251. + else
  252. + return json_interp_socket(upi, buf, buflen);
  253. +}
  254. - return ULOGD_IRET_OK;
  255. +static void reopen_file(struct ulogd_pluginstance *upi)
  256. +{
  257. + struct json_priv *oi = (struct json_priv *) &upi->private;
  258. + FILE *old = oi->of;
  259. +
  260. + ulogd_log(ULOGD_NOTICE, "JSON: reopening logfile\n");
  261. + oi->of = fopen(upi->config_kset->ces[0].u.string, "a");
  262. + if (!oi->of) {
  263. + ulogd_log(ULOGD_ERROR, "can't open JSON "
  264. + "log file: %s\n",
  265. + strerror(errno));
  266. + oi->of = old;
  267. + } else {
  268. + fclose(old);
  269. + }
  270. +}
  271. +
  272. +static void reopen_socket(struct ulogd_pluginstance *upi)
  273. +{
  274. + ulogd_log(ULOGD_NOTICE, "JSON: reopening socket\n");
  275. + if (_connect_socket(upi) < 0) {
  276. + ulogd_log(ULOGD_ERROR, "can't open JSON "
  277. + "socket: %s\n",
  278. + strerror(errno));
  279. + }
  280. }
  281. static void sighup_handler_print(struct ulogd_pluginstance *upi, int signal)
  282. {
  283. struct json_priv *oi = (struct json_priv *) &upi->private;
  284. - FILE *old = oi->of;
  285. switch (signal) {
  286. case SIGHUP:
  287. - ulogd_log(ULOGD_NOTICE, "JSON: reopening logfile\n");
  288. - oi->of = fopen(upi->config_kset->ces[0].u.string, "a");
  289. - if (!oi->of) {
  290. - ulogd_log(ULOGD_ERROR, "can't open JSON "
  291. - "log file: %s\n",
  292. - strerror(errno));
  293. - oi->of = old;
  294. - } else {
  295. - fclose(old);
  296. - }
  297. + if (oi->mode == JSON_MODE_FILE)
  298. + reopen_file(upi);
  299. + else
  300. + reopen_socket(upi);
  301. break;
  302. default:
  303. break;
  304. @@ -255,6 +459,8 @@ static void sighup_handler_print(struct
  305. static int json_configure(struct ulogd_pluginstance *upi,
  306. struct ulogd_pluginstance_stack *stack)
  307. {
  308. + struct json_priv *op = (struct json_priv *) &upi->private;
  309. + char *mode_str = mode_ce(upi->config_kset).u.string;
  310. int ret;
  311. ret = ulogd_wildcard_inputkeys(upi);
  312. @@ -265,13 +471,25 @@ static int json_configure(struct ulogd_p
  313. if (ret < 0)
  314. return ret;
  315. + if (!strcasecmp(mode_str, "udp")) {
  316. + op->mode = JSON_MODE_UDP;
  317. + } else if (!strcasecmp(mode_str, "tcp")) {
  318. + op->mode = JSON_MODE_TCP;
  319. + } else if (!strcasecmp(mode_str, "unix")) {
  320. + op->mode = JSON_MODE_UNIX;
  321. + } else if (!strcasecmp(mode_str, "file")) {
  322. + op->mode = JSON_MODE_FILE;
  323. + } else {
  324. + ulogd_log(ULOGD_ERROR, "unknown mode '%s'\n", mode_str);
  325. + return -EINVAL;
  326. + }
  327. +
  328. return 0;
  329. }
  330. -static int json_init(struct ulogd_pluginstance *upi)
  331. +static int json_init_file(struct ulogd_pluginstance *upi)
  332. {
  333. struct json_priv *op = (struct json_priv *) &upi->private;
  334. - unsigned int i;
  335. op->of = fopen(upi->config_kset->ces[0].u.string, "a");
  336. if (!op->of) {
  337. @@ -280,6 +498,27 @@ static int json_init(struct ulogd_plugin
  338. return -1;
  339. }
  340. + return 0;
  341. +}
  342. +
  343. +static int json_init_socket(struct ulogd_pluginstance *upi)
  344. +{
  345. + struct json_priv *op = (struct json_priv *) &upi->private;
  346. +
  347. + if (host_ce(upi->config_kset).u.string == NULL)
  348. + return -1;
  349. + if (port_ce(upi->config_kset).u.string == NULL)
  350. + return -1;
  351. +
  352. + op->sock = -1;
  353. + return _connect_socket(upi);
  354. +}
  355. +
  356. +static int json_init(struct ulogd_pluginstance *upi)
  357. +{
  358. + struct json_priv *op = (struct json_priv *) &upi->private;
  359. + unsigned int i;
  360. +
  361. /* search for time */
  362. op->sec_idx = -1;
  363. op->usec_idx = -1;
  364. @@ -293,15 +532,25 @@ static int json_init(struct ulogd_plugin
  365. *op->cached_tz = '\0';
  366. - return 0;
  367. + if (op->mode == JSON_MODE_FILE)
  368. + return json_init_file(upi);
  369. + else
  370. + return json_init_socket(upi);
  371. +}
  372. +
  373. +static void close_file(FILE *of) {
  374. + if (of != stdout)
  375. + fclose(of);
  376. }
  377. static int json_fini(struct ulogd_pluginstance *pi)
  378. {
  379. struct json_priv *op = (struct json_priv *) &pi->private;
  380. - if (op->of != stdout)
  381. - fclose(op->of);
  382. + if (op->mode == JSON_MODE_FILE)
  383. + close_file(op->of);
  384. + else
  385. + close_socket(op);
  386. return 0;
  387. }
  388. --- a/ulogd.conf.in
  389. +++ b/ulogd.conf.in
  390. @@ -213,6 +213,17 @@ sync=1
  391. # Uncomment the following line to use JSON v1 event format that
  392. # can provide better compatility with some JSON file reader.
  393. #eventv1=1
  394. +# Uncomment the following lines to send the JSON logs to a remote host via UDP
  395. +#mode="udp"
  396. +#host="192.0.2.10"
  397. +#port="10210"
  398. +# Uncomment the following lines to send the JSON logs to a remote host via TCP
  399. +#mode="tcp"
  400. +#host="192.0.2.10"
  401. +#port="10210"
  402. +# Uncomment the following lines to send the JSON logs to a local unix socket
  403. +#mode="unix"
  404. +#file="/var/run/ulogd.socket"
  405. [pcap1]
  406. #default file is /var/log/ulogd.pcap