diff --git a/sysdep/unix/config.Y b/sysdep/unix/config.Y
index af82e5bd..bec8d7dc 100644
--- a/sysdep/unix/config.Y
+++ b/sysdep/unix/config.Y
@@ -20,11 +20,12 @@ 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)
+CF_KEYWORDS(UDP, HOST, PORT)
 
 %type <i> log_mask log_mask_list log_cat cfg_timeout
 %type <t> cfg_name
 %type <tf> timeformat_which
-%type <t> syslog_name
+%type <t> syslog_name log_udp_server_name log_udp_server_port
 
 CF_GRAMMAR
 
@@ -58,8 +59,36 @@ log_file:
    }
  | SYSLOG syslog_name { this_log->fh = NULL; new_config->syslog_name = $2; }
  | STDERR { this_log->fh = stderr; }
+ | UDP log_udp_server_name log_udp_server_port {
+     this_log->rf = rf_open_udp(new_config->pool, $2, $3);
+     if (!this_log->rf) cf_error("Unable to open udp log '%s %s': %m", $2, $3);
+     this_log->fh = rf_file(this_log->rf);
+     this_log->pos = -1;
+     this_log->filename = NULL;
+   }
  ;
 
+log_udp_server_name:
+    /* empty */ { $$ = "127.0.0.1"; }
+  | HOST ipa {
+      char *server_name = cfg_allocz(IPA_MAX_TEXT_LENGTH+1);
+      bsnprintf(server_name, IPA_MAX_TEXT_LENGTH+1, "%I", $2);
+      $$ = server_name;
+    }
+  | HOST text { $$ = $2; }
+  ;
+
+log_udp_server_port:
+    /* empty */ { $$ = "514"; }
+  | PORT NUM {
+      check_u16($2);
+      char *server_port = cfg_allocz(32);
+      bsnprintf(server_port, 32, "%u", $2);
+      $$ = server_port;
+    }
+  | PORT text { $$ = $2; }
+  ;
+
 log_mask:
    ALL { $$ = ~0; }
  | '{' log_mask_list '}' { $$ = $2; }
diff --git a/sysdep/unix/io.c b/sysdep/unix/io.c
index 5e4d9573..c98fe8a6 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"
@@ -103,6 +104,54 @@ rf_open(pool *p, char *name, char *mode)
   return r;
 }
 
+struct rfile *
+rf_open_udp(pool *p, char *server_hostname, char *server_port)
+{
+  int gai_error;
+  struct addrinfo hints;
+  struct addrinfo *ai;
+  int s;
+
+  memset(&hints, 0, sizeof(struct addrinfo));
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_DGRAM;
+  hints.ai_flags = 0;
+  hints.ai_protocol = 0;
+
+  gai_error = getaddrinfo(server_hostname, server_port, &hints, &ai);
+  if (gai_error != 0)
+    return NULL;
+
+  s = socket(ai->ai_family,
+             ai->ai_socktype,
+             ai->ai_protocol);
+  if (s < 0)
+  {
+    freeaddrinfo(ai);
+    return NULL;
+  }
+
+  if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
+  {
+    freeaddrinfo(ai);
+    return NULL;
+  }
+
+  freeaddrinfo(ai);
+
+  FILE *f = fdopen(s, "a");
+
+  if (!f)
+  {
+    close(s);
+    return NULL;
+  }
+
+  struct rfile *r = ralloc(p, &rf_class);
+  r->f = f;
+  return r;
+}
+
 void *
 rf_file(struct rfile *f)
 {
diff --git a/sysdep/unix/unix.h b/sysdep/unix/unix.h
index bd817bf2..7d88e6f6 100644
--- a/sysdep/unix/unix.h
+++ b/sysdep/unix/unix.h
@@ -107,6 +107,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 *, char *name, char *mode);
+struct rfile *rf_open_udp(struct pool *, char *server_hostname, char *server_port);
 void *rf_file(struct rfile *f);
 int rf_fileno(struct rfile *f);
 void test_old_bird(char *path);
