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.

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