commit cbc332b64149ed67af5e2d0d2f9f3ab2f78f708b
Author: Alexander Zubkov <green@qrator.net>
Date:   Sun Jan 2 14:14:49 2022 +0100

    Log: add udp log destination
    
    Add UDP log destination, which can be configured with:
    
    log udp ["hostname"|<ip>] [<port>] <classes>;

diff --git a/lib/socket.h b/lib/socket.h
index 96fedeeb..642a56ce 100644
--- a/lib/socket.h
+++ b/lib/socket.h
@@ -84,6 +84,7 @@ typedef struct birdsock {
 sock *sock_new(pool *);			/* Allocate new socket */
 #define sk_new(X) sock_new(X)		/* Wrapper to avoid name collision with OpenSSL */
 
+int sk_hostname_autoresolv(sock *);	/* Auto-resolve an IP address from a hostname */
 int sk_open(sock *);			/* Open socket */
 int sk_rx_ready(sock *s);
 int sk_send(sock *, uint len);		/* Send data, <0=err, >0=ok, 0=sleep */
diff --git a/sysdep/unix/config.Y b/sysdep/unix/config.Y
index 5c4b5bef..a14d39e4 100644
--- a/sysdep/unix/config.Y
+++ b/sysdep/unix/config.Y
@@ -15,9 +15,15 @@ CF_DEFINES
 
 static struct log_config *this_log;
 
+static struct {
+  ip_addr ip;
+  const char *host;
+  uint port;
+} log_udp_cfg;
+
 CF_DECLS
 
-CF_KEYWORDS(LOG, SYSLOG, ALL, DEBUG, TRACE, INFO, REMOTE, WARNING, ERROR, AUTH, FATAL, BUG, STDERR, SOFT)
+CF_KEYWORDS(LOG, SYSLOG, UDP, 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)
 
@@ -61,8 +67,41 @@ log_file:
    }
  | SYSLOG syslog_name { this_log->fh = NULL; new_config->syslog_name = $2; }
  | STDERR { this_log->fh = stderr; }
+ | log_udp {
+     sock *sk = sk_new(new_config->pool);
+     sk->type = SK_UDP;
+     sk->daddr = log_udp_cfg.ip;
+     sk->host = log_udp_cfg.host;
+     sk->dport = log_udp_cfg.port;
+
+     this_log->rf = rf_open_sk(new_config->pool, sk);
+     if (!this_log->rf)
+       cf_error("Unable to open udp log");
+     this_log->fh = rf_file(this_log->rf);
+     this_log->pos = -1;
+     this_log->filename = NULL;
+   }
  ;
 
+log_udp: log_udp_start log_udp_host log_udp_port;
+
+log_udp_start: UDP {
+  log_udp_cfg.ip = IPA_NONE;
+  log_udp_cfg.host = NULL;
+  log_udp_cfg.port = 0;
+};
+
+log_udp_host:
+    /* empty */ { log_udp_cfg.ip = ipa_build4(127, 0, 0, 1); }
+  | ipa { log_udp_cfg.ip = $1; }
+  | text { log_udp_cfg.host = $1; }
+  ;
+
+log_udp_port:
+    /* empty */ { log_udp_cfg.port = 514; }
+  | NUM { check_u16($1); log_udp_cfg.port = $1; }
+  ;
+
 log_mask:
    ALL { $$ = ~0; }
  | '{' log_mask_list '}' { $$ = $2; }
diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c
index 3d67d0a7..baccb3b3 100644
--- a/sysdep/unix/io.c
+++ b/sysdep/unix/io.c
@@ -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"
@@ -55,6 +56,7 @@
    this to gen small latencies */
 #define MAX_RX_STEPS 4
 
+int sk_write(sock *);
 
 /*
  *	Tracked Files
@@ -103,6 +105,27 @@ rf_open(pool *p, const char *name, const char *mode)
   return r;
 }
 
+struct rfile *
+rf_open_sk(pool *p, sock *s)
+{
+  if (sk_hostname_autoresolv(s) < 0)
+    return NULL;
+  if (sk_open(s) < 0)
+    return NULL;
+  if (sk_write(s) != 0)
+    return NULL;
+
+  FILE *f = fdopen(s->fd, "a");
+  if (!f)
+    return NULL;
+
+  struct rfile *r = ralloc(p, &rf_class);
+  r->f = f;
+  s->fd = -1;
+
+  return r;
+}
+
 void *
 rf_file(struct rfile *f)
 {
@@ -1309,6 +1332,40 @@ sk_open_ssh(sock *s)
 }
 #endif
 
+/*
+ * Resolve the hostname in case IP is not defined.
+ */
+int
+sk_hostname_autoresolv(sock *s)
+{
+  if (ipa_zero(s->daddr) && s->host)
+  {
+    struct addrinfo *res;
+    struct addrinfo hints = {
+        .ai_family = AF_UNSPEC,
+        .ai_socktype = (s->type == SK_UDP) ? SOCK_DGRAM : SOCK_STREAM,
+        .ai_flags = AI_ADDRCONFIG,
+    };
+
+    int err_code = getaddrinfo(s->host, NULL, &hints, &res);
+    if (err_code != 0)
+    {
+      log(L_ERR "Cannot resolve hostname '%s': %s",
+          s->host, gai_strerror(err_code));
+      return -1;
+    }
+
+    ip_addr addr = IPA_NONE;
+    uint unused;
+    sockaddr_read((sockaddr *) res->ai_addr, res->ai_family, &addr, NULL, &unused);
+    freeaddrinfo(res);
+
+    s->daddr = addr;
+  }
+
+  return 0;
+}
+
 /**
  * sk_open - open a socket
  * @s: socket
@@ -1919,12 +1976,15 @@ sk_write(sock *s)
   switch (s->type)
   {
   case SK_TCP_ACTIVE:
+  case SK_UDP:
     {
       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)
-	sk_tcp_connected(s);
+      {
+	if (s->type != SK_UDP) sk_tcp_connected(s);
+      }
       else if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS)
 	s->err_hook(s, errno);
       return 0;
diff --git a/sysdep/unix/unix.h b/sysdep/unix/unix.h
index ad85d1ea..621c3236 100644
--- a/sysdep/unix/unix.h
+++ b/sysdep/unix/unix.h
@@ -109,6 +109,7 @@ 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_open_sk(struct pool *, struct birdsock *);
 void *rf_file(struct rfile *f);
 int rf_fileno(struct rfile *f);
 void test_old_bird(char *path);
