]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Log: Add support for UDP logging
authorOndrej Zajicek <santiago@crfreenet.org>
Wed, 13 Dec 2023 02:48:12 +0000 (03:48 +0100)
committerOndrej Zajicek <santiago@crfreenet.org>
Wed, 13 Dec 2023 03:01:09 +0000 (04:01 +0100)
Add support for UDP logging, using RFC 3164 syslog protocol.

Based on the patch from Alexander Zubkov <green@qrator.net>, thanks!

doc/bird.sgml
lib/socket.h
sysdep/unix/config.Y
sysdep/unix/io.c
sysdep/unix/log.c
sysdep/unix/unix.h

index 419a453761aad360db9abef4a3dc1f6a87d12973..79d5873f8fe64b877053e9a2dba7afbef62d33da 100644 (file)
@@ -543,11 +543,12 @@ ipv6 table
 include "tablename.conf";;
 </code>
 
-       <tag><label id="opt-log">log "<m/filename/" [<m/limit/ "<m/backup/"] | syslog [name <m/name/] | stderr all|{ <m/list of classes/ }</tag>
+       <tag><label id="opt-log">log "<m/filename/" [<m/limit/ "<m/backup/"] | syslog [name <m/name/] | stderr | udp <m/address/ [port <m/port/] all|{ <m/list of classes/ }</tag>
        Set logging of messages having the given class (either <cf/all/ or <cf>{
        error|trace [, <m/.../] }</cf> etc.) into selected destination - a file
        specified as a filename string (with optional log rotation information),
-       syslog (with optional name argument), or the stderr output.
+       syslog (with optional name argument), the stderr output, or as a UDP
+       message (in <rfc id="3164"> syslog format).
 
        Classes are:
        <cf/info/, <cf/warning/, <cf/error/ and <cf/fatal/ for messages about local problems,
index 0b6ac5898d6d0db42bf393b8d06fb7e8f2fbc39b..231c10d86d7c6fb71715921ae2711452941c46b1 100644 (file)
@@ -124,6 +124,7 @@ extern int sk_priority_control;             /* Suggested priority for control traffic, shou
 #define SKF_BIND       0x10    /* Bind datagram socket to given source address */
 #define SKF_HIGH_PORT  0x20    /* Choose port from high range if possible */
 #define SKF_FREEBIND   0x40    /* Allow socket to bind to a nonlocal address */
+#define SKF_CONNECT    0x80    /* Connect datagram socket to given dst address/port */
 
 #define SKF_THREAD     0x100   /* Socked used in thread, Do not add to main loop */
 #define SKF_TRUNCATED  0x200   /* Received packet was truncated, set by IO layer */
index fce647943a0afb57cfa0d894f2455fbfbe7651f5..8de79600ed35b0b2b3ceaa481f0e2e984cf6a69b 100644 (file)
@@ -17,9 +17,9 @@ static struct log_config *this_log;
 
 CF_DECLS
 
-CF_KEYWORDS(LOG, SYSLOG, ALL, DEBUG, TRACE, INFO, REMOTE, WARNING, ERROR, AUTH, FATAL, BUG, STDERR, SOFT)
-CF_KEYWORDS(NAME, CONFIRM, UNDO, CHECK, TIMEOUT, DEBUG, LATENCY, LIMIT, WATCHDOG, WARNING, STATUS)
-CF_KEYWORDS(GRACEFUL, RESTART, THREADS)
+CF_KEYWORDS(LOG, SYSLOG, NAME, STDERR, UDP, PORT)
+CF_KEYWORDS(ALL, DEBUG, TRACE, INFO, REMOTE, WARNING, ERROR, AUTH, FATAL, BUG)
+CF_KEYWORDS(DEBUG, LATENCY, LIMIT, WATCHDOG, WARNING, TIMEOUT, THREADS)
 
 %type <i> log_mask log_mask_list log_cat cfg_timeout
 %type <t> cfg_name
@@ -64,8 +64,28 @@ log_file:
    }
  | SYSLOG syslog_name { this_log->fh = NULL; new_config->syslog_name = $2; }
  | STDERR { this_log->fh = stderr; }
+ | UDP log_udp_host log_udp_port {
+     this_log->udp_flag = 1;
+
+     if (!parse_and_exit)
+       log_open_udp(this_log, new_config->pool);
+   }
  ;
 
+log_udp_host: text_or_ipa
+{
+  if ($1.type == T_STRING)
+    this_log->host = $1.val.s;
+  else if ($1.type == T_IP)
+    this_log->ip = $1.val.ip;
+  else bug("Bad text_or_ipa");
+};
+
+log_udp_port:
+    /* empty */ { this_log->port = 514; }
+  | PORT NUM { check_u16($2); this_log->port = $2; }
+  ;
+
 log_mask:
    ALL { $$ = ~0; }
  | '{' log_mask_list '}' { $$ = $2; }
index 4b3eef4862f195d6af59035402e17fd5bb68838e..9b499020423b97a689202de7840ef428a7042ef2 100644 (file)
@@ -30,6 +30,7 @@
 #include <netinet/tcp.h>
 #include <netinet/udp.h>
 #include <netinet/icmp6.h>
+#include <netdb.h>
 
 #include "nest/bird.h"
 #include "lib/lists.h"
@@ -94,12 +95,25 @@ struct rfile *
 rf_open(pool *p, const char *name, const char *mode)
 {
   FILE *f = fopen(name, mode);
+  if (!f)
+    return NULL;
+
+  struct rfile *r = ralloc(p, &rf_class);
+  r->f = f;
 
+  return r;
+}
+
+struct rfile *
+rf_fdopen(pool *p, int fd, const char *mode)
+{
+  FILE *f = fdopen(fd, mode);
   if (!f)
     return NULL;
 
   struct rfile *r = ralloc(p, &rf_class);
   r->f = f;
+
   return r;
 }
 
@@ -1048,6 +1062,14 @@ sk_insert(sock *s)
   add_tail(&sock_list, &s->n);
 }
 
+static int
+sk_connect(sock *s)
+{
+  sockaddr sa;
+  sockaddr_fill(&sa, s->af, s->daddr, s->iface, s->dport);
+  return connect(s->fd, &sa.sa, SA_LEN(sa));
+}
+
 static void
 sk_tcp_connected(sock *s)
 {
@@ -1465,8 +1487,7 @@ sk_open(sock *s)
   switch (s->type)
   {
   case SK_TCP_ACTIVE:
-    sockaddr_fill(&sa, s->af, s->daddr, s->iface, s->dport);
-    if (connect(fd, &sa.sa, SA_LEN(sa)) >= 0)
+    if (sk_connect(s) >= 0)
       sk_tcp_connected(s);
     else if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS &&
             errno != ECONNREFUSED && errno != EHOSTUNREACH && errno != ENETUNREACH)
@@ -1478,6 +1499,14 @@ sk_open(sock *s)
       ERR2("listen");
     break;
 
+  case SK_UDP:
+    if (s->flags & SKF_CONNECT)
+      if (sk_connect(s) < 0)
+       ERR2("connect");
+
+    sk_alloc_bufs(s);
+    break;
+
   case SK_SSH_ACTIVE:
   case SK_MAGIC:
     break;
@@ -1945,10 +1974,7 @@ sk_write_noflush(sock *s)
   {
   case SK_TCP_ACTIVE:
     {
-      sockaddr sa;
-      sockaddr_fill(&sa, s->af, s->daddr, s->iface, s->dport);
-
-      if (connect(s->fd, &sa.sa, SA_LEN(sa)) >= 0 || errno == EISCONN)
+      if (sk_connect(s) >= 0 || errno == EISCONN)
        sk_tcp_connected(s);
       else if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS)
        s->err_hook(s, errno);
@@ -2418,3 +2444,36 @@ test_old_bird(char *path)
     die("I found another BIRD running.");
   close(fd);
 }
+
+
+/*
+ *     DNS resolver
+ */
+
+ip_addr
+resolve_hostname(const char *host, int type, const char **err_msg)
+{
+  struct addrinfo *res;
+  struct addrinfo hints = {
+    .ai_family = AF_UNSPEC,
+    .ai_socktype = (type == SK_UDP) ? SOCK_DGRAM : SOCK_STREAM,
+    .ai_flags = AI_ADDRCONFIG,
+  };
+
+  *err_msg = NULL;
+
+  int err_code = getaddrinfo(host, NULL, &hints, &res);
+  if (err_code != 0)
+  {
+    *err_msg = gai_strerror(err_code);
+    return IPA_NONE;
+  }
+
+  ip_addr addr = IPA_NONE;
+  uint unused;
+
+  sockaddr_read((sockaddr *) res->ai_addr, res->ai_family, &addr, NULL, &unused);
+  freeaddrinfo(res);
+
+  return addr;
+}
index 53122aee3b98696cec871c2bc68f2cdf0b49d192..613a6aa54c914ddab7cd47fbbcd5293c571e9d98 100644 (file)
@@ -29,6 +29,7 @@
 #include "conf/conf.h"
 #include "lib/string.h"
 #include "lib/lists.h"
+#include "lib/socket.h"
 #include "sysdep/unix/unix.h"
 
 static int dbg_fd = -1;
@@ -138,6 +139,55 @@ log_rotate(struct log_config *l)
   return log_open(l);
 }
 
+/* Expected to be called during config parsing */
+int
+log_open_udp(struct log_config *l, pool *p)
+{
+  ASSERT(l->host || ipa_nonzero(l->ip));
+
+  if (l->host && ipa_zero(l->ip))
+  {
+    const char *err_msg;
+    l->ip = resolve_hostname(l->host, SK_UDP, &err_msg);
+
+    if (ipa_zero(l->ip))
+    {
+      cf_warn("Cannot resolve hostname '%s': %s", l->host, err_msg);
+      goto err0;
+    }
+  }
+
+  sock *sk = sk_new(p);
+  sk->type = SK_UDP;
+  sk->daddr = l->ip;
+  sk->dport = l->port;
+  sk->flags = SKF_CONNECT | SKF_THREAD;
+
+  if (sk_open(sk) < 0)
+  {
+    cf_warn("Cannot open UDP log socket: %s%#m", sk->err);
+    goto err1;
+  }
+
+  /* Move fd from sk resource to rf resource */
+  l->rf = rf_fdopen(p, sk->fd, "a");
+  if (!l->rf)
+    goto err1;
+
+  l->fh = rf_file(l->rf);
+
+  sk->fd = -1;
+  rfree(sk);
+
+  return 0;
+
+err1:
+  rfree(sk);
+err0:
+  l->mask = 0;
+  return -1;
+}
+
 /**
  * log_commit - commit a log message
  * @class: message class information (%L_DEBUG to %L_BUG, see |lib/birdlib.h|)
@@ -168,6 +218,18 @@ log_commit(int class, buffer *buf)
        {
          if (l->terminal_flag)
            fputs("bird: ", l->fh);
+         else if (l->udp_flag)
+           {
+             int pri = LOG_DAEMON | syslog_priorities[class];
+             char tbuf[TM_DATETIME_BUFFER_SIZE];
+             const char *hostname = (config && config->hostname) ? config->hostname : "<none>";
+             const char *fmt = "%b %d %T.%6f";
+             if (!tm_format_real_time(tbuf, sizeof(tbuf), fmt, current_real_time()))
+               strcpy(tbuf, "<error>");
+
+             /* Legacy RFC 3164 format, but with us precision */
+             fprintf(l->fh, "<%d>%s %s %s: ", pri, tbuf, hostname, bird_name);
+           }
          else
            {
              byte tbuf[TM_DATETIME_BUFFER_SIZE];
@@ -400,7 +462,7 @@ log_switch(int initial, list *logs, const char *new_syslog_name)
   /* Close the logs to avoid pinning them on disk when deleted */
   if (current_log_list)
     WALK_LIST(l, *current_log_list)
-      if (l->rf)
+      if (l->filename && l->rf)
        log_close(l);
 
   /* Reopen the logs, needed for 'configure undo' */
index ad85d1ea5fd6dd4849acda381e32202e04525894..c1d966f903cbaa3c3361c43053cd02835f37d319 100644 (file)
@@ -109,9 +109,11 @@ void io_loop(void);
 void io_log_dump(void);
 int sk_open_unix(struct birdsock *s, char *name);
 struct rfile *rf_open(struct pool *, const char *name, const char *mode);
+struct rfile *rf_fdopen(pool *p, int fd, const char *mode);
 void *rf_file(struct rfile *f);
 int rf_fileno(struct rfile *f);
 void test_old_bird(char *path);
+ip_addr resolve_hostname(const char *host, int type, const char **err_msg);
 
 /* krt.c bits */
 
@@ -133,6 +135,12 @@ struct log_config {
   off_t pos;                           /* Position/size of current log */
   off_t limit;                         /* Log size limit */
   int terminal_flag;
+  int udp_flag;
+  const char *host;                    /* UDP log dst host name */
+  ip_addr ip;                          /* UDP log dst IP address */
+  uint port;                           /* UDP log dst port */
 };
 
+int log_open_udp(struct log_config *l, pool *p);
+
 #endif