]> git.proxmox.com Git - pve-firewall.git/blobdiff - src/pvefw-logger.c
fix #2178: endless loop on ipv6 extension headers
[pve-firewall.git] / src / pvefw-logger.c
index 3b79ed1ad08ea7fbd9fd01e5711a2ecc775e9c6b..181d5f1e8b7520b008b7761943ce9d04f6e3b6cc 100644 (file)
@@ -40,6 +40,7 @@
 #include <linux/netlink.h>
 #include <libnfnetlink/libnfnetlink.h>
 #include <libnetfilter_log/libnetfilter_log.h>
+#include <libnetfilter_conntrack/libnetfilter_conntrack.h>
 #include <netinet/ip.h>
 #include <netinet/ip_icmp.h>
 #include <netinet/ip6.h>
 
 static struct nflog_handle *logh = NULL;
 static struct nlif_handle *nlifh = NULL;
+static struct nfct_handle *nfcth = NULL;
 GMainLoop *main_loop;
 
 gboolean foreground = FALSE;
 gboolean debug = FALSE;
+gboolean conntrack = FALSE;
 
 /*
 
@@ -76,6 +79,7 @@ Example:
 
 #define LOCKFILE "/var/lock/pvefw-logger.lck"
 #define PIDFILE "/var/run/pvefw-logger.pid"
+#define LOG_CONNTRACK_FILE "/var/lib/pve-firewall/log_nf_conntrack"
 
 #define LQ_LEN 512
 #define LE_MAX (512 - 4) // try to fit into 512 bytes
@@ -192,8 +196,7 @@ queue_log_entry(struct log_entry *le)
 static void
 log_status_message(guint loglevel, const char *fmt, ...)
 {
-    va_list ap;
-    va_start(ap, fmt);
+    va_list ap, ap2;
 
     if (loglevel > 7 ) loglevel = 7; // syslog defines level 0-7
 
@@ -203,7 +206,10 @@ log_status_message(guint loglevel, const char *fmt, ...)
 
     LEPRINTTIME(time(NULL));
 
+    va_start(ap, fmt);
+    va_copy(ap2, ap);
     le->len += vsnprintf(le->buf + le->len, LE_MAX - le->len, fmt, ap);
+    va_end(ap);
 
     LEPRINTF("\n");
 
@@ -211,7 +217,8 @@ log_status_message(guint loglevel, const char *fmt, ...)
 
     // also log to syslog
 
-    vsyslog(loglevel, fmt, ap);
+    vsyslog(loglevel, fmt, ap2);
+    va_end(ap2);
 }
 
 static int
@@ -568,6 +575,7 @@ print_nexthdr(struct log_entry *le, char *hdr, int payload_len, u_int8_t proto)
             return 0;
 
         struct ip6_ext *exthdr = (struct ip6_ext*)hdr;
+        int ext_len = 0;
 
         switch (proto) {
         /* protocols (these return) */
@@ -594,6 +602,7 @@ print_nexthdr(struct log_entry *le, char *hdr, int payload_len, u_int8_t proto)
                 return -1;
             if (print_fragment(le, (struct ip6_frag*)hdr, payload_len) < 0)
                 return -1;
+            ext_len = sizeof(struct ip6_frag);
             break;
         case IPPROTO_HOPOPTS:
             LEPRINTF("NEXTHDR=HOPOPTS ");
@@ -621,8 +630,12 @@ print_nexthdr(struct log_entry *le, char *hdr, int payload_len, u_int8_t proto)
         /* next header: */
         if (check_ip6ext(le, exthdr, payload_len) < 0)
             return -1;
-        hdr += exthdr->ip6e_len;
-        payload_len -= exthdr->ip6e_len;
+        if(ext_len == 0) {
+            ext_len = (exthdr->ip6e_len+1) * 8;
+        }
+        hdr += ext_len;
+        payload_len -= ext_len;
+        proto = exthdr->ip6e_nxt;
     }
 }
 
@@ -753,9 +766,11 @@ static int print_pkt(struct log_entry *le, struct nflog_data *ldata, u_int8_t fa
     LEPRINTF("%s ", chain_name);
 
     struct timeval ts;
-    nflog_get_timestamp(ldata, &ts);
-
-    LEPRINTTIME(ts.tv_sec);
+    if (nflog_get_timestamp(ldata, &ts) == 0) {
+        LEPRINTTIME(ts.tv_sec);
+    } else {
+        LEPRINTTIME(time(NULL));
+    }
 
     if (prefix != NULL) {
         LEPRINTF("%s", prefix);
@@ -915,6 +930,46 @@ signal_read_cb(GIOChannel *source,
     return TRUE;
 }
 
+static int
+nfct_cb(const struct nlmsghdr *nlh,
+        enum nf_conntrack_msg_type type,
+        struct nf_conntrack *ct,
+        void *data)
+{
+    struct log_entry *le = g_new0(struct log_entry, 1);
+    int len = nfct_snprintf(&le->buf[le->len], LE_MAX - le->len,
+                            ct, type, NFCT_O_DEFAULT,
+                            NFCT_OF_SHOW_LAYER3|NFCT_OF_TIMESTAMP);
+    le->len += len;
+
+    if (le->len == LE_MAX) {
+        le->buf[le->len-1] = '\n';
+    } else { // le->len < LE_MAX
+        le->buf[le->len++] = '\n';
+    }
+
+    queue_log_entry(le);
+
+    return NFCT_CB_STOP;
+}
+
+static gboolean
+nfct_read_cb(GIOChannel *source,
+             GIOCondition condition,
+             gpointer data)
+{
+    int res;
+    if ((res = nfct_catch(nfcth)) < 0) {
+        if (errno == ENOBUFS) {
+            log_status_message(3, "nfct_catch returned ENOBUFS: conntrack information may be incomplete");
+        } else {
+            log_status_message(3, "error catching nfct: %s", strerror(errno));
+            return FALSE;
+        }
+    }
+    return TRUE;
+}
+
 int
 main(int argc, char *argv[])
 {
@@ -923,13 +978,14 @@ main(int argc, char *argv[])
 
     gboolean wrote_pidfile = FALSE;
 
-    openlog("pvepw-logger", LOG_CONS|LOG_PID, LOG_DAEMON);
+    openlog("pvefw-logger", LOG_CONS|LOG_PID, LOG_DAEMON);
 
     GOptionContext *context;
 
     GOptionEntry entries[] = {
         { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, "Turn on debug messages", NULL },
         { "foreground", 'f', 0, G_OPTION_ARG_NONE, &foreground, "Do not daemonize server", NULL },
+        { "conntrack", 0, 0, G_OPTION_ARG_NONE, &conntrack, "Add conntrack logging", NULL },
         { NULL },
     };
 
@@ -952,6 +1008,23 @@ main(int argc, char *argv[])
 
     g_option_context_free(context);
 
+    if (!conntrack) {
+        int log_nf_conntrackfd = open(LOG_CONNTRACK_FILE, O_RDONLY);
+        if (log_nf_conntrackfd == -1) {
+            if (errno != ENOENT) {
+                fprintf(stderr, "error: failed to open "LOG_CONNTRACK_FILE": %s\n", strerror(errno));
+            }
+        } else {
+            char c = '0';
+            ssize_t bytes = read(log_nf_conntrackfd, &c, sizeof(c));
+            if (bytes < 0) {
+                fprintf(stderr, "error: failed to read value in log_nf_conntrack: %s\n", strerror(errno));
+            } else {
+                conntrack = (c == '1');
+            }
+        }
+    }
+
     if (debug) foreground = TRUE;
 
     if ((lockfd = open(LOCKFILE, O_RDWR|O_CREAT|O_APPEND, 0644)) == -1) {
@@ -982,7 +1055,7 @@ main(int argc, char *argv[])
         exit(-1);
     }
 
-    if (!nflog_bind_pf(logh, AF_INET) <= 0) {
+    if (nflog_bind_pf(logh, AF_INET) < 0) {
         fprintf(stderr, "nflog_bind_pf AF_INET failed\n");
         exit(-1);
     }
@@ -994,7 +1067,7 @@ main(int argc, char *argv[])
     }
 #endif
 
-    if (!nflog_bind_pf(logh, AF_BRIDGE) <= 0) {
+    if (nflog_bind_pf(logh, AF_BRIDGE) < 0) {
         fprintf(stderr, "nflog_bind_pf AF_BRIDGE failed\n");
         exit(-1);
     }
@@ -1015,6 +1088,13 @@ main(int argc, char *argv[])
         exit(-1);
     }
 
+    if (conntrack) {
+        if ((nfcth = nfct_open(CONNTRACK, NF_NETLINK_CONNTRACK_NEW|NF_NETLINK_CONNTRACK_DESTROY)) == NULL) {
+            fprintf(stderr, "unable to open netfilter conntrack\n");
+            exit(-1);
+        }
+    }
+
     sigset_t mask;
     sigemptyset(&mask);
     sigaddset(&mask, SIGINT);
@@ -1074,6 +1154,13 @@ main(int argc, char *argv[])
 
     g_io_add_watch(nflog_ch, G_IO_IN, nflog_read_cb, NULL);
 
+    if (conntrack) {
+        nfct_callback_register2(nfcth, NFCT_T_NEW|NFCT_T_DESTROY, &nfct_cb, NULL);
+        int nfctfd = nfct_fd(nfcth);
+        GIOChannel *nfct_ch = g_io_channel_unix_new(nfctfd);
+        g_io_add_watch(nfct_ch, G_IO_IN, nfct_read_cb, NULL);
+    }
+
     GIOChannel *sig_ch = g_io_channel_unix_new(sigfd);
     if (!g_io_add_watch(sig_ch, G_IO_IN, signal_read_cb, NULL)) {
         exit(-1);
@@ -1091,6 +1178,11 @@ main(int argc, char *argv[])
 
     close(outfd);
 
+    if (conntrack) {
+        nfct_callback_unregister2(nfcth);
+        nfct_close(nfcth);
+    }
+
     nflog_close(logh);
 
     if (wrote_pidfile)