rtl_433/src/output_udp.c

240 lines
6.3 KiB
C

/** @file
UDP syslog output for rtl_433 events.
Copyright (C) 2021 Christian Zuckschwerdt
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
*/
#include "output_udp.h"
#include "data.h"
#include "abuf.h"
#include "r_util.h"
#include "logger.h"
#include "fatal.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
// _POSIX_HOST_NAME_MAX is broken in gcc-13 at least on MacOS
#ifndef _POSIX_HOST_NAME_MAX
//#warning The limits.h include is missing the _POSIX_HOST_NAME_MAX define.
#define _POSIX_HOST_NAME_MAX 255
#endif
// gethostname() needs _XOPEN_SOURCE 500 on unistd.h
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500
#endif
#ifndef _MSC_VER
#include <unistd.h>
#endif
#ifdef _WIN32
#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600)
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0600 /* Needed to pull in 'struct sockaddr_storage' */
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#define SOCKET int
#define INVALID_SOCKET (-1)
#define closesocket(x) close(x)
#endif
#include <time.h>
#ifdef _WIN32
#define _POSIX_HOST_NAME_MAX 128
#define perror(str) ws2_perror(str)
static void ws2_perror (const char *str)
{
if (str && *str)
fprintf(stderr, "%s: ", str);
fprintf(stderr, "Winsock error %d.\n", WSAGetLastError());
}
#endif
#ifdef ESP32
#include <tcpip_adapter.h>
#define _POSIX_HOST_NAME_MAX 128
#define gai_strerror strerror
#endif
/* Datagram (UDP) client */
typedef struct {
struct sockaddr_storage addr;
socklen_t addr_len;
SOCKET sock;
} datagram_client_t;
static int datagram_client_open(datagram_client_t *client, const char *host, const char *port)
{
if (!host || !port)
return -1;
struct addrinfo hints, *res, *res0;
int error;
SOCKET sock;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_ADDRCONFIG;
error = getaddrinfo(host, port, &hints, &res0);
if (error) {
print_log(LOG_ERROR, __func__, gai_strerror(error));
return -1;
}
sock = INVALID_SOCKET;
for (res = res0; res; res = res->ai_next) {
sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (sock >= 0) {
client->sock = sock;
memset(&client->addr, 0, sizeof(client->addr));
memcpy(&client->addr, res->ai_addr, res->ai_addrlen);
client->addr_len = res->ai_addrlen;
break; // success
}
}
freeaddrinfo(res0);
if (sock == INVALID_SOCKET) {
perror("socket");
return -1;
}
//int broadcast = 1;
//int ret = setsockopt(client->sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
return 0;
}
static void datagram_client_close(datagram_client_t *client)
{
if (!client)
return;
if (client->sock != INVALID_SOCKET) {
closesocket(client->sock);
client->sock = INVALID_SOCKET;
}
#ifdef _WIN32
WSACleanup();
#endif
}
static void datagram_client_send(datagram_client_t *client, const char *message, size_t message_len)
{
int r = sendto(client->sock, message, message_len, 0, (struct sockaddr *)&client->addr, client->addr_len);
if (r == -1) {
perror("sendto");
}
}
/* Syslog UDP printer, RFC 5424 (IETF-syslog protocol) */
typedef struct {
struct data_output output;
datagram_client_t client;
int pri;
char hostname[_POSIX_HOST_NAME_MAX + 1];
} data_output_syslog_t;
static void R_API_CALLCONV data_output_syslog_print(data_output_t *output, data_t *data)
{
data_output_syslog_t *syslog = (data_output_syslog_t *)output;
// we expect a normal message around 500 bytes
// full stats report would be 12k and we want a max of MTU anyway
char message[1024];
abuf_t msg = {0};
abuf_init(&msg, message, sizeof(message));
time_t now;
struct tm tm_info;
time(&now);
#ifdef _WIN32
gmtime_s(&tm_info, &now);
#else
gmtime_r(&now, &tm_info);
#endif
char timestamp[21];
strftime(timestamp, 21, "%Y-%m-%dT%H:%M:%SZ", &tm_info);
abuf_printf(&msg, "<%d>1 %s %s rtl_433 - - - ", syslog->pri, timestamp, syslog->hostname);
msg.tail += data_print_jsons(data, msg.tail, msg.left);
if (msg.tail >= msg.head + sizeof(message))
return; // abort on overflow, we don't actually want to send more than fits the MTU
size_t abuf_len = msg.tail - msg.head;
datagram_client_send(&syslog->client, message, abuf_len);
}
static void R_API_CALLCONV data_output_syslog_free(data_output_t *output)
{
data_output_syslog_t *syslog = (data_output_syslog_t *)output;
if (!syslog)
return;
datagram_client_close(&syslog->client);
free(syslog);
}
struct data_output *data_output_syslog_create(int log_level, const char *host, const char *port)
{
data_output_syslog_t *syslog = calloc(1, sizeof(data_output_syslog_t));
if (!syslog) {
WARN_CALLOC("data_output_syslog_create()");
return NULL; // NOTE: returns NULL on alloc failure.
}
#ifdef _WIN32
WSADATA wsa;
if (WSAStartup(MAKEWORD(2,2),&wsa) != 0) {
perror("WSAStartup()");
free(syslog);
return NULL;
}
#endif
syslog->output.log_level = log_level;
syslog->output.output_print = data_output_syslog_print;
syslog->output.output_free = data_output_syslog_free;
// Severity 5 "Notice", Facility 20 "local use 4"
syslog->pri = 20 * 8 + 5;
#ifdef ESP32
const char* adapter_hostname = NULL;
tcpip_adapter_get_hostname(TCPIP_ADAPTER_IF_STA, &adapter_hostname);
if (adapter_hostname) {
memcpy(syslog->hostname, adapter_hostname, _POSIX_HOST_NAME_MAX);
}
else {
syslog->hostname[0] = '\0';
}
#else
gethostname(syslog->hostname, _POSIX_HOST_NAME_MAX + 1);
#endif
syslog->hostname[_POSIX_HOST_NAME_MAX] = '\0';
datagram_client_open(&syslog->client, host, port);
return (struct data_output *)syslog;
}