MEDIUM: log: adds log forwarding section.

Log forwarding:

It is possible to declare one or multiple log forwarding section,
haproxy will forward all received log messages to a log servers list.

log-forward <name>
  Creates a new log forwarder proxy identified as <name>.

bind <addr> [param*]
  Used to configure a log udp listener to receive messages to forward.
  Only udp listeners are allowed, address must be prefixed using
  'udp@', 'udp4@' or 'udp6@'. This supports for all "bind" parameters
  found in 5.1 paragraph but most of them are irrelevant for udp/syslog case.

log global
log <address> [len <length>] [format <format>] [sample <ranges>:<smp_size>]
    <facility> [<level> [<minlevel>]]
  Used to configure target log servers. See more details on proxies
  documentation.
  If no format specified, haproxy tries to keep the incoming log format.
  Configured facility is ignored, except if incoming message does not
  present a facility but one is mandatory on the outgoing format.
  If there is no timestamp available in the input format, but the field
  exists in output format, haproxy will use the local date.

  Example:
    global
       log stderr format iso local7

    ring myring
        description "My local buffer"
        format rfc5424
        maxlen 1200
        size 32764
        timeout connect 5s
        timeout server 10s
        # syslog tcp server
        server mysyslogsrv 127.0.0.1:514 log-proto octet-count

    log-forward sylog-loadb
        bind udp4@127.0.0.1:1514
        # all messages on stderr
        log global
        # all messages on local tcp syslog server
        log ring@myring local0
        # load balance messages on 4 udp syslog servers
        log 127.0.0.1:10001 sample 1:4 local0
        log 127.0.0.1:10002 sample 2:4 local0
        log 127.0.0.1:10003 sample 3:4 local0
        log 127.0.0.1:10004 sample 4:4 local0
diff --git a/doc/configuration.txt b/doc/configuration.txt
index 0b3d564..2a4672b 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -2738,6 +2738,57 @@
         timeout server 10s
         server mysyslogsrv 127.0.0.1:6514 log-proto octet-count
 
+3.10. Log forwarding
+-------------------
+
+It is possible to declare one or multiple log forwarding section,
+haproxy will forward all received log messages to a log servers list.
+
+log-forward <name>
+  Creates a new log forwarder proxy identified as <name>.
+
+bind <addr> [param*]
+  Used to configure a log udp listener to receive messages to forward.
+  Only udp listeners are allowed, address must be prefixed using
+  'udp@', 'udp4@' or 'udp6@'. This supports for all "bind" parameters
+  found in 5.1 paragraph but most of them are irrelevant for udp/syslog case.
+
+log global
+log <address> [len <length>] [format <format>] [sample <ranges>:<smp_size>]
+    <facility> [<level> [<minlevel>]]
+  Used to configure target log servers. See more details on proxies
+  documentation.
+  If no format specified, haproxy tries to keep the incoming log format.
+  Configured facility is ignored, except if incoming message does not
+  present a facility but one is mandatory on the outgoing format.
+  If there is no timestamp available in the input format, but the field
+  exists in output format, haproxy will use the local date.
+
+  Example:
+    global
+       log stderr format iso local7
+
+    ring myring
+        description "My local buffer"
+        format rfc5424
+        maxlen 1200
+        size 32764
+        timeout connect 5s
+        timeout server 10s
+        # syslog tcp server
+        server mysyslogsrv 127.0.0.1:514 log-proto octet-count
+
+    log-forward sylog-loadb
+        bind udp4@127.0.0.1:1514
+        # all messages on stderr
+        log global
+        # all messages on local tcp syslog server
+        log ring@myring local0
+        # load balance messages on 4 udp syslog servers
+        log 127.0.0.1:10001 sample 1:4 local0
+        log 127.0.0.1:10002 sample 2:4 local0
+        log 127.0.0.1:10003 sample 3:4 local0
+        log 127.0.0.1:10004 sample 4:4 local0
 
 4. Proxies
 ----------
@@ -3323,14 +3374,14 @@
                     - 'ipv4@'  -> address is always IPv4
                     - 'ipv6@'  -> address is always IPv6
                     - 'udp@'   -> address is resolved as IPv4 or IPv6 and
-                      protocol UDP is used. Currently there is no proxy
-                      mode supporting those listeners.
+                      protocol UDP is used. Currently those listeners are
+                      supported only in log-forward sections.
                     - 'udp4@'  -> address is always IPv4 and protocol UDP
-                      is used. Currently there is no proxy mode supporting
-                      those listeners.
+                      is used. Currently those listeners are supported
+                      only in log-forward sections.
                     - 'udp6@'  -> address is always IPv6 and protocol UDP
-                      is used. Currently there is no proxy mode supporting
-                      those listeners.
+                      is used. Currently those listeners are supported
+                      only in log-forward sections.
                     - 'unix@'  -> address is a path to a local unix socket
                     - 'abns@'  -> address is in abstract namespace (Linux only).
                       Note: since abstract sockets are not "rebindable", they
diff --git a/include/haproxy/log.h b/include/haproxy/log.h
index 683c135..d8b95a8 100644
--- a/include/haproxy/log.h
+++ b/include/haproxy/log.h
@@ -43,6 +43,9 @@
 
 extern unsigned int dropped_logs;
 
+/* lof forward proxy list */
+extern struct proxy *cfg_log_forward;
+
 extern THREAD_LOCAL char *logline;
 extern THREAD_LOCAL char *logline_rfc5424;
 
diff --git a/src/log.c b/src/log.c
index b6d5c45..f8ac522 100644
--- a/src/log.c
+++ b/src/log.c
@@ -26,12 +26,15 @@
 
 #include <haproxy/api.h>
 #include <haproxy/applet-t.h>
+#include <haproxy/cfgparse.h>
 #include <haproxy/cli.h>
 #include <haproxy/fd.h>
 #include <haproxy/frontend.h>
 #include <haproxy/global.h>
 #include <haproxy/http.h>
+#include <haproxy/listener.h>
 #include <haproxy/log.h>
+#include <haproxy/proxy.h>
 #include <haproxy/ring.h>
 #include <haproxy/sample.h>
 #include <haproxy/sink.h>
@@ -43,6 +46,9 @@
 #include <haproxy/version.h>
 
 
+/* log forward proxy list */
+struct proxy *cfg_log_forward;
+
 struct log_fmt_st {
 	char *name;
 };
@@ -3543,6 +3549,144 @@
 	return;
 }
 
+/*
+ * Parse "log-forward" section and create corresponding sink buffer.
+ *
+ * The function returns 0 in success case, otherwise, it returns error
+ * flags.
+ */
+int cfg_parse_log_forward(const char *file, int linenum, char **args, int kwm)
+{
+	int err_code = 0;
+	struct proxy *px;
+	char *errmsg = NULL;
+	const char *err = NULL;
+
+	if (strcmp(args[0], "log-forward") == 0) {
+		if (!*args[1]) {
+			ha_alert("parsing [%s:%d] : missing name for ip-forward section.\n", file, linenum);
+			err_code |= ERR_ALERT | ERR_ABORT;
+			goto out;
+		}
+
+		if (alertif_too_many_args(1, file, linenum, args, &err_code))
+			goto out;
+
+		err = invalid_char(args[1]);
+		if (err) {
+			ha_alert("parsing [%s:%d] : character '%c' is not permitted in '%s' name '%s'.\n",
+			         file, linenum, *err, args[0], args[1]);
+			err_code |= ERR_ALERT | ERR_ABORT;
+			goto out;
+		}
+
+		for (px = cfg_log_forward ; px ; px = px->next) {
+			if (strcmp(px->id, args[1]) == 0) {
+				ha_alert("Parsing [%s:%d]: log-forward section '%s' has the same name as another log-forward section declared at %s:%d.\n",
+					 file, linenum, args[1], px->conf.file, px->conf.line);
+				err_code |= ERR_ALERT | ERR_FATAL;
+			}
+		}
+
+		px = calloc(1, sizeof *px);
+		if (!px) {
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+
+		px->next = cfg_log_forward;
+		cfg_log_forward = px;
+
+		init_new_proxy(px);
+		px->conf.file = strdup(file);
+		px->conf.line = linenum;
+		px->mode = PR_MODE_SYSLOG;
+		px->id = strdup(args[1]);
+
+	}
+	else if (strcmp(args[0], "bind") == 0) {
+		int cur_arg;
+		static int kws_dumped;
+		struct bind_conf *bind_conf;
+		struct bind_kw *kw;
+		struct listener *l;
+
+		cur_arg = 1;
+
+		bind_conf = bind_conf_alloc(cfg_log_forward, file, linenum,
+		                            NULL, xprt_get(XPRT_RAW));
+
+		if (!str2listener(args[1], cfg_log_forward, bind_conf, file, linenum, &errmsg)) {
+			if (errmsg && *errmsg) {
+				indent_msg(&errmsg, 2);
+				ha_alert("parsing [%s:%d] : '%s %s' : %s\n", file, linenum, args[0], args[1], errmsg);
+			}
+			else {
+				ha_alert("parsing [%s:%d] : '%s %s' : error encountered while parsing listening address %s.\n",
+				         file, linenum, args[0], args[1], args[2]);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+		}
+		list_for_each_entry(l, &bind_conf->listeners, by_bind) {
+			/* Currently, only UDP handlers are allowed */
+			if (l->proto->sock_domain != AF_CUST_UDP4 && l->proto->sock_domain != AF_CUST_UDP6) {
+				ha_alert("parsing [%s:%d] : '%s %s' : error,  listening address must be prefixed using 'udp@', 'udp4@' or 'udp6@' %s.\n",
+				         file, linenum, args[0], args[1], args[2]);
+				err_code |= ERR_ALERT | ERR_FATAL;
+				goto out;
+			}
+			l->maxaccept = global.tune.maxaccept ? global.tune.maxaccept : 64;
+			global.maxsock++;
+		}
+		cur_arg++;
+
+		while (*args[cur_arg] && (kw = bind_find_kw(args[cur_arg]))) {
+			int ret;
+
+			ret = kw->parse(args, cur_arg, cfg_log_forward, bind_conf, &errmsg);
+			err_code |= ret;
+			if (ret) {
+				if (errmsg && *errmsg) {
+					indent_msg(&errmsg, 2);
+					ha_alert("parsing [%s:%d] : %s\n", file, linenum, errmsg);
+				}
+				else
+					ha_alert("parsing [%s:%d]: error encountered while processing '%s'\n",
+					         file, linenum, args[cur_arg]);
+				if (ret & ERR_FATAL)
+					goto out;
+			}
+			cur_arg += 1 + kw->skip;
+		}
+		if (*args[cur_arg] != 0) {
+			char *kws = NULL;
+
+			if (!kws_dumped) {
+				kws_dumped = 1;
+				bind_dump_kws(&kws);
+				indent_msg(&kws, 4);
+			}
+			ha_alert("parsing [%s:%d] : unknown keyword '%s' in '%s' section.%s%s\n",
+			         file, linenum, args[cur_arg], cursection,
+				 kws ? " Registered keywords :" : "", kws ? kws: "");
+			free(kws);
+			err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+	}
+	else if (strcmp(args[0], "log") == 0) {
+		if (!parse_logsrv(args, &cfg_log_forward->logsrvs, (kwm == KWM_NO), &errmsg)) {
+			ha_alert("parsing [%s:%d] : %s : %s\n", file, linenum, args[0], errmsg);
+			         err_code |= ERR_ALERT | ERR_FATAL;
+			goto out;
+		}
+	}
+out:
+	return err_code;
+}
+
+
 /* parse the "show startup-logs" command, returns 1 if a message is returned, otherwise zero */
 static int cli_parse_show_startup_logs(char **args, char *payload, struct appctx *appctx, void *private)
 {
@@ -3564,6 +3708,9 @@
 
 INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);
 
+/* config parsers for this section */
+REGISTER_CONFIG_SECTION("log-forward", cfg_parse_log_forward, NULL);
+
 REGISTER_PER_THREAD_ALLOC(init_log_buffers);
 REGISTER_PER_THREAD_FREE(deinit_log_buffers);
 
diff --git a/src/sink.c b/src/sink.c
index eb9a657..4b5dd5b 100644
--- a/src/sink.c
+++ b/src/sink.c
@@ -1001,6 +1001,19 @@
 			}
 		}
 	}
+
+	for (px = cfg_log_forward; px; px = px->next) {
+		list_for_each_entry_safe(logsrv, logb, &px->logsrvs, list) {
+			if (logsrv->type == LOG_TARGET_BUFFER) {
+				sink = sink_find(logsrv->ring_name);
+				if (!sink || sink->type != SINK_TYPE_BUFFER) {
+					ha_alert("log-forward '%s' log server uses unknown ring named '%s'.\n", px->id, logsrv->ring_name);
+					err_code |= ERR_ALERT | ERR_FATAL;
+				}
+				logsrv->sink = sink;
+			}
+		}
+	}
 	return err_code;
 }