syslog: Add networking backend

If network based syslog backend is enabled in Kconfig file,
then syslog messages are sent to external system using UDP.
See RFC 5424 and RFC 5426 for details about the syslog protocol.

Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
This commit is contained in:
Jukka Rissanen 2018-01-24 20:54:15 +02:00
commit 9f73d5e2fd
3 changed files with 274 additions and 0 deletions

View file

@ -4,3 +4,4 @@ zephyr_sources_ifdef(
event_logger.c
kernel_event_logger.c
)
zephyr_sources_ifdef(CONFIG_SYS_LOG_BACKEND_NET sys_log_net.c)

View file

@ -72,5 +72,53 @@ config SYS_LOG_EXT_HOOK
default n
help
Use external hook function for logging.
config SYS_LOG_BACKEND_NET
bool "Networking syslog backend"
default n
depends on SYS_LOG && NETWORKING
select SYS_LOG_EXT_HOOK
select NET_CONTEXT_NET_PKT_POOL
help
Send syslog messages to network server.
See RFC 5424 (syslog protocol) and RFC 5426 (syslog over UDP)
specifications for details.
if SYS_LOG_BACKEND_NET
config SYS_LOG_BACKEND_NET_SERVER
string "Syslog server IP address"
help
This can be either IPv4 or IPv6 address.
Server listen UDP port number can be configured here too.
Following syntax is supported:
192.0.2.1:514
192.0.2.42
[2001:db8::1]:514
[2001:db8::2]
2001:db::42
config SYS_LOG_BACKEND_NET_MAX_BUF
int "How many network buffers to allocate for sending messages"
range 3 256
default 3
help
Each syslog message will occupy one network buffer.
config SYS_LOG_BACKEND_NET_MAX_BUF_SIZE
int "Max syslog message size"
range 64 1180
default 256
help
As each syslog message needs to fit to UDP packet, set this value
so that messages are not truncated.
The RFC 5426 recommends that for IPv4 the size is 480 octets and for
IPv6 the size is 1180 octets. As each buffer will use RAM, the value
should be selected so that typical messages will fit the buffer.
The total allocated memory will be
SYS_LOG_BACKEND_NET_MAX_BUF * SYS_LOG_BACKEND_NET_MAX_BUF_SIZE
endif
endmenu

View file

@ -0,0 +1,225 @@
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <logging/sys_log.h>
#include <net/net_pkt.h>
#include <net/net_context.h>
/* Set this to 1 if you want to see what is being sent to server */
#define DEBUG_PRINTING 0
static struct net_context *ctx;
static struct sockaddr server_addr;
/* FIXME: As there is no way to figure out these values in the hook
* function, use some pre-defined values. Change this to use the
* real facility and severity of the logging call when that info is
* available.
*/
static const int facility = 16; /* local0 */
static const int severity = 6; /* info */
#define DATE_EPOCH "1970-01-01T00:00:00.000000-00:00"
static char date[sizeof(DATE_EPOCH)];
#if defined(CONFIG_NET_IPV6) || CONFIG_NET_HOSTNAME_ENABLE
#define MAX_HOSTNAME_LEN NET_IPV6_ADDR_LEN
#else
#define MAX_HOSTNAME_LEN NET_IPV4_ADDR_LEN
#endif
static char hostname[MAX_HOSTNAME_LEN + 1];
NET_PKT_SLAB_DEFINE(syslog_tx_pkts, CONFIG_SYS_LOG_BACKEND_NET_MAX_BUF);
NET_BUF_POOL_DEFINE(syslog_tx_bufs, CONFIG_SYS_LOG_BACKEND_NET_MAX_BUF,
CONFIG_SYS_LOG_BACKEND_NET_MAX_BUF_SIZE,
CONFIG_NET_BUF_USER_DATA_SIZE, NULL);
static struct k_mem_slab *get_tx_slab(void)
{
return &syslog_tx_pkts;
}
struct net_buf_pool *get_data_pool(void)
{
return &syslog_tx_bufs;
}
static void fill_header(struct net_buf *buf)
{
snprintk(net_buf_tail(buf),
net_buf_tailroom(buf),
"<%d>1 %s %s - - - - ",
facility * 8 + severity,
date,
hostname);
net_buf_add(buf, strlen(buf->data));
}
static void syslog_hook_net(const char *fmt, ...)
{
struct net_pkt *pkt;
struct net_buf *frag;
u8_t *ptr;
va_list vargs;
int ret;
pkt = net_pkt_get_tx(ctx, K_NO_WAIT);
if (!pkt) {
return;
}
frag = net_pkt_get_data(ctx, K_NO_WAIT);
if (!frag) {
net_pkt_unref(pkt);
return;
}
net_pkt_frag_add(pkt, frag);
fill_header(frag);
va_start(vargs, fmt);
ptr = net_buf_tail(frag);
ret = vsnprintk(ptr, (net_buf_tailroom(frag) - 1), fmt, vargs);
if (ret < 0) {
net_pkt_unref(pkt);
return;
}
va_end(vargs);
if (ret > 0 && ptr[ret - 1] == '\n') {
/* No need to send \n to peer so strip it away */
ret--;
}
net_buf_add(frag, ret);
#if DEBUG_PRINTING
{
static u32_t count;
printk("%d:%s", ++count, frag->data);
}
#endif
ret = net_context_send(pkt, NULL, K_NO_WAIT, NULL, NULL);
if (ret < 0) {
net_pkt_unref(pkt);
}
}
void syslog_net_hook_install(void)
{
#if defined(CONFIG_NET_IPV6)
struct sockaddr_in6 local_addr6 = {
.sin6_family = AF_INET6,
.sin6_port = 0,
};
#endif
#if defined(CONFIG_NET_IPV4)
struct sockaddr_in local_addr4 = {
.sin_family = AF_INET,
.sin_port = 0,
};
#endif
struct sockaddr *local_addr = NULL;
socklen_t local_addr_len = 0;
socklen_t server_addr_len = 0;
int ret;
net_sin(&server_addr)->sin_port = htons(514);
ret = net_ipaddr_parse(CONFIG_SYS_LOG_BACKEND_NET_SERVER,
sizeof(CONFIG_SYS_LOG_BACKEND_NET_SERVER) - 1,
&server_addr);
if (!ret) {
SYS_LOG_ERR("Cannot configure syslog server address");
return;
}
#if defined(CONFIG_NET_IPV4)
if (server_addr.sa_family == AF_INET) {
local_addr = (struct sockaddr *)&local_addr4;
local_addr_len = sizeof(struct sockaddr_in);
server_addr_len = sizeof(struct sockaddr_in);
}
#endif
#if defined(CONFIG_NET_IPV6)
if (server_addr.sa_family == AF_INET6) {
local_addr = (struct sockaddr *)&local_addr6;
local_addr_len = sizeof(struct sockaddr_in6);
server_addr_len = sizeof(struct sockaddr_in6);
}
#endif
ret = net_context_get(server_addr.sa_family, SOCK_DGRAM, IPPROTO_UDP,
&ctx);
if (ret < 0) {
SYS_LOG_ERR("Cannot get context (%d)", ret);
return;
}
#if CONFIG_NET_HOSTNAME_ENABLE
memcpy(hostname, net_hostname_get(), MAX_HOSTNAME_LEN);
#else /* CONFIG_NET_HOSTNAME_ENABLE */
if (server_addr.sa_family == AF_INET6) {
#if defined(CONFIG_NET_IPV6)
const struct in6_addr *src;
src = net_if_ipv6_select_src_addr(
NULL, &net_sin6(&server_addr)->sin6_addr);
if (src) {
net_addr_ntop(AF_INET6, src, hostname,
MAX_HOSTNAME_LEN);
net_ipaddr_copy(&local_addr6.sin6_addr, src);
} else {
goto unknown;
}
#else
goto unknown;
#endif
} else if (server_addr.sa_family == AF_INET) {
#if defined(CONFIG_NET_IPV4)
/* FIXME: take the first IPv4 address of an interface */
net_ipaddr_copy(&local_addr4.sin_addr,
&net_if_get_default()->ipv4.unicast[0].address.in_addr);
net_addr_ntop(AF_INET, &local_addr4.sin_addr, hostname,
MAX_HOSTNAME_LEN);
#else
goto unknown;
#endif
} else {
unknown:
strncpy(hostname, "zephyr", MAX_HOSTNAME_LEN);
}
#endif /* CONFIG_NET_HOSTNAME_ENABLE */
ret = net_context_bind(ctx, local_addr, local_addr_len);
if (ret < 0) {
SYS_LOG_ERR("Cannot bind context (%d)", ret);
return;
}
ret = net_context_connect(ctx, &server_addr, server_addr_len,
NULL, K_NO_WAIT, NULL);
/* We do not care about return value for this UDP connect call that
* basically does nothing. Calling the connect is only useful so that
* we can see the syslog connection in net-shell.
*/
net_context_setup_pools(ctx, get_tx_slab, get_data_pool);
syslog_hook_install(syslog_hook_net);
}