1 /** @file
2 UDP syslog output for rtl_433 events.
3
4 Copyright (C) 2021 Christian Zuckschwerdt
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 */
11
12 #include "output_udp.h"
13
14 #include "data.h"
15 #include "abuf.h"
16 #include "r_util.h"
17 #include "fatal.h"
18
19 #include <string.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22
23 #include <limits.h>
24 // gethostname() needs _XOPEN_SOURCE 500 on unistd.h
25 #ifndef _XOPEN_SOURCE
26 #define _XOPEN_SOURCE 500
27 #endif
28
29 #ifndef _MSC_VER
30 #include <unistd.h>
31 #endif
32
33 #ifdef _WIN32
34 #if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600)
35 #undef _WIN32_WINNT
36 #define _WIN32_WINNT 0x0600 /* Needed to pull in 'struct sockaddr_storage' */
37 #endif
38
39 #include <winsock2.h>
40 #include <ws2tcpip.h>
41 #else
42 #include <sys/types.h>
43 #include <sys/socket.h>
44 #include <netdb.h>
45 #include <netinet/in.h>
46
47 #define SOCKET int
48 #define INVALID_SOCKET (-1)
49 #define closesocket(x) close(x)
50 #endif
51
52 #include <time.h>
53
54 #ifdef _WIN32
55 #define _POSIX_HOST_NAME_MAX 128
56 #define perror(str) ws2_perror(str)
57
ws2_perror(const char * str)58 static void ws2_perror (const char *str)
59 {
60 if (str && *str)
61 fprintf(stderr, "%s: ", str);
62 fprintf(stderr, "Winsock error %d.\n", WSAGetLastError());
63 }
64 #endif
65 #ifdef ESP32
66 #include <tcpip_adapter.h>
67 #define _POSIX_HOST_NAME_MAX 128
68 #define gai_strerror strerror
69 #endif
70
71 /* Datagram (UDP) client */
72
73 typedef struct {
74 struct sockaddr_storage addr;
75 socklen_t addr_len;
76 SOCKET sock;
77 } datagram_client_t;
78
datagram_client_open(datagram_client_t * client,const char * host,const char * port)79 static int datagram_client_open(datagram_client_t *client, const char *host, const char *port)
80 {
81 if (!host || !port)
82 return -1;
83
84 struct addrinfo hints, *res, *res0;
85 int error;
86 SOCKET sock;
87
88 memset(&hints, 0, sizeof(hints));
89 hints.ai_family = PF_UNSPEC;
90 hints.ai_socktype = SOCK_DGRAM;
91 hints.ai_flags = AI_ADDRCONFIG;
92 error = getaddrinfo(host, port, &hints, &res0);
93 if (error) {
94 fprintf(stderr, "%s\n", gai_strerror(error));
95 return -1;
96 }
97 sock = INVALID_SOCKET;
98 for (res = res0; res; res = res->ai_next) {
99 sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
100 if (sock >= 0) {
101 client->sock = sock;
102 memset(&client->addr, 0, sizeof(client->addr));
103 memcpy(&client->addr, res->ai_addr, res->ai_addrlen);
104 client->addr_len = res->ai_addrlen;
105 break; // success
106 }
107 }
108 freeaddrinfo(res0);
109 if (sock == INVALID_SOCKET) {
110 perror("socket");
111 return -1;
112 }
113
114 //int broadcast = 1;
115 //int ret = setsockopt(client->sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
116
117 return 0;
118 }
119
datagram_client_close(datagram_client_t * client)120 static void datagram_client_close(datagram_client_t *client)
121 {
122 if (!client)
123 return;
124
125 if (client->sock != INVALID_SOCKET) {
126 closesocket(client->sock);
127 client->sock = INVALID_SOCKET;
128 }
129
130 #ifdef _WIN32
131 WSACleanup();
132 #endif
133 }
134
datagram_client_send(datagram_client_t * client,const char * message,size_t message_len)135 static void datagram_client_send(datagram_client_t *client, const char *message, size_t message_len)
136 {
137 int r = sendto(client->sock, message, message_len, 0, (struct sockaddr *)&client->addr, client->addr_len);
138 if (r == -1) {
139 perror("sendto");
140 }
141 }
142
143 /* Syslog UDP printer, RFC 5424 (IETF-syslog protocol) */
144
145 typedef struct {
146 struct data_output output;
147 datagram_client_t client;
148 int pri;
149 char hostname[_POSIX_HOST_NAME_MAX + 1];
150 } data_output_syslog_t;
151
print_syslog_data(data_output_t * output,data_t * data,char const * format)152 static void print_syslog_data(data_output_t *output, data_t *data, char const *format)
153 {
154 UNUSED(format);
155 data_output_syslog_t *syslog = (data_output_syslog_t *)output;
156
157 // we expect a normal message around 500 bytes
158 // full stats report would be 12k and we want a max of MTU anyway
159 char message[1024];
160 abuf_t msg = {0};
161 abuf_init(&msg, message, sizeof(message));
162
163 time_t now;
164 struct tm tm_info;
165 time(&now);
166 #ifdef _WIN32
167 gmtime_s(&tm_info, &now);
168 #else
169 gmtime_r(&now, &tm_info);
170 #endif
171 char timestamp[21];
172 strftime(timestamp, 21, "%Y-%m-%dT%H:%M:%SZ", &tm_info);
173
174 abuf_printf(&msg, "<%d>1 %s %s rtl_433 - - - ", syslog->pri, timestamp, syslog->hostname);
175
176 msg.tail += data_print_jsons(data, msg.tail, msg.left);
177 if (msg.tail >= msg.head + sizeof(message))
178 return; // abort on overflow, we don't actually want to send more than fits the MTU
179
180 size_t abuf_len = msg.tail - msg.head;
181 datagram_client_send(&syslog->client, message, abuf_len);
182 }
183
data_output_syslog_free(data_output_t * output)184 static void data_output_syslog_free(data_output_t *output)
185 {
186 data_output_syslog_t *syslog = (data_output_syslog_t *)output;
187
188 if (!syslog)
189 return;
190
191 datagram_client_close(&syslog->client);
192
193 free(syslog);
194 }
195
data_output_syslog_create(const char * host,const char * port)196 struct data_output *data_output_syslog_create(const char *host, const char *port)
197 {
198 data_output_syslog_t *syslog = calloc(1, sizeof(data_output_syslog_t));
199 if (!syslog) {
200 WARN_CALLOC("data_output_syslog_create()");
201 return NULL; // NOTE: returns NULL on alloc failure.
202 }
203 #ifdef _WIN32
204 WSADATA wsa;
205
206 if (WSAStartup(MAKEWORD(2,2),&wsa) != 0) {
207 perror("WSAStartup()");
208 free(syslog);
209 return NULL;
210 }
211 #endif
212
213 syslog->output.print_data = print_syslog_data;
214 syslog->output.output_free = data_output_syslog_free;
215 // Severity 5 "Notice", Facility 20 "local use 4"
216 syslog->pri = 20 * 8 + 5;
217 #ifdef ESP32
218 const char* adapter_hostname = NULL;
219 tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &adapter_hostname);
220 if (adapter_hostname) {
221 memcpy(syslog->hostname, adapter_hostname, _POSIX_HOST_NAME_MAX);
222 }
223 else {
224 syslog->hostname[0] = '\0';
225 }
226 #else
227 gethostname(syslog->hostname, _POSIX_HOST_NAME_MAX + 1);
228 #endif
229 syslog->hostname[_POSIX_HOST_NAME_MAX] = '\0';
230 datagram_client_open(&syslog->client, host, port);
231
232 return &syslog->output;
233 }
234