]> git.proxmox.com Git - pve-firewall.git/commitdiff
add simple nflog daemon
authorDietmar Maurer <dietmar@proxmox.com>
Thu, 13 Mar 2014 12:08:47 +0000 (13:08 +0100)
committerDietmar Maurer <dietmar@proxmox.com>
Thu, 13 Mar 2014 12:34:23 +0000 (13:34 +0100)
Makefile
debian/changelog
debian/control
debian/install
debian/pve-firewall.logrotate [new file with mode: 0644]
debian/rules
src/Makefile
src/PVE/Firewall.pm
src/pvefw-logger.c [new file with mode: 0644]

index 7180795f6f749d3ebce39bab2d3e08e064ea8bbe..79fe413bf369af3d017bdb790c7909f75df1534b 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,7 +1,7 @@
 RELEASE=3.3
 
 VERSION=1.0
 RELEASE=3.3
 
 VERSION=1.0
-PKGREL=1
+PKGREL=2
 
 PACKAGE=pve-firewall
 
 
 PACKAGE=pve-firewall
 
@@ -13,7 +13,7 @@ DOCDIR=${PREFIX}/share/doc
 MAN1DIR=${MANDIR}/man1/
 PERLDIR=${PREFIX}/share/perl5
 
 MAN1DIR=${MANDIR}/man1/
 PERLDIR=${PREFIX}/share/perl5
 
-ARCH=all
+ARCH=amd64
 GITVERSION:=$(shell cat .git/refs/heads/master)
 
 DEB=${PACKAGE}_${VERSION}-${PKGREL}_${ARCH}.deb
 GITVERSION:=$(shell cat .git/refs/heads/master)
 
 DEB=${PACKAGE}_${VERSION}-${PKGREL}_${ARCH}.deb
@@ -26,7 +26,7 @@ dinstall: deb
 
 
 .PHONY: deb
 
 
 .PHONY: deb
-deb ${DEB}: 
+deb ${DEB}:
        rm -rf build
        rsync -a src/ build
        rsync -a debian/ build/debian
        rm -rf build
        rsync -a src/ build
        rsync -a debian/ build/debian
@@ -52,4 +52,3 @@ upload: ${DEB}
        cp ${DEB} /pve/${RELEASE}/extra
        cd /pve/${RELEASE}/extra; dpkg-scanpackages . /dev/null > Packages; gzip -9c Packages > Packages.gz
        umount /pve/${RELEASE}; mount /pve/${RELEASE} -o ro
        cp ${DEB} /pve/${RELEASE}/extra
        cd /pve/${RELEASE}/extra; dpkg-scanpackages . /dev/null > Packages; gzip -9c Packages > Packages.gz
        umount /pve/${RELEASE}; mount /pve/${RELEASE} -o ro
-
index 0c99eafdc18318a76f58ffdfbebba832012de48c..597ed0adf8420405aaf2ba342a2862e2750a7183 100644 (file)
@@ -1,3 +1,9 @@
+pve-firewall (1.0-2) unstable; urgency=low
+
+  * add experimental nflog logging daemon
+
+ -- Proxmox Support Team <support@proxmox.com>  Thu, 13 Mar 2014 08:27:01 +0100
+
 pve-firewall (1.0-1) unstable; urgency=low
 
   * initial package
 pve-firewall (1.0-1) unstable; urgency=low
 
   * initial package
index 1387783963088d3a5eb639363e752fff76b8a9ef..42e3f7b66e287424cc627d0238d80a8f2fd6cea1 100644 (file)
@@ -2,11 +2,12 @@ Source: pve-firewall
 Section: admin
 Priority: extra
 Maintainer: Proxmox Support Team <support@proxmox.com>
 Section: admin
 Priority: extra
 Maintainer: Proxmox Support Team <support@proxmox.com>
-Build-Depends: debhelper (>= 7.0.50~)
+Build-Depends: debhelper (>= 7.0.50~), libnetfilter-log-dev, libglib2.0-dev
 Standards-Version: 3.8.4
 
 Package: pve-firewall
 Standards-Version: 3.8.4
 
 Package: pve-firewall
-Architecture: all
-Depends: ${perl:Depends}, ${misc:Depends}, libpve-common-perl, qemu-server
+Architecture: any
+Conflicts: ulogd
+Depends: ${shlibs:Depends}, ${perl:Depends}, ${misc:Depends}, libpve-common-perl, qemu-server
 Description: Proxmox VE Firewall
  This package contains the Proxmox VE Firewall.
 Description: Proxmox VE Firewall
  This package contains the Proxmox VE Firewall.
index 2ab287d70c61de47e2213cd3f24e65fb0f2538bd..29fa088dafe2545ed003e0b0a63cbf0b6664de86 100644 (file)
@@ -1 +1 @@
-debian/ifupdown.sh usr/share/pve-firewall/scripts
\ No newline at end of file
+debian/ifupdown.sh usr/share/pve-firewall/scripts
diff --git a/debian/pve-firewall.logrotate b/debian/pve-firewall.logrotate
new file mode 100644 (file)
index 0000000..13ee0eb
--- /dev/null
@@ -0,0 +1,13 @@
+/var/log/pve-firewall.log {
+    rotate 7
+    daily
+    missingok
+    notifempty 
+    delaycompress
+    compress
+    sharedscripts
+    create 640 root adm
+    postrotate
+       invoke-rc.d pvefw-logger restart 2>/dev/null >/dev/null || true
+    endscript
+}
index b760bee7f4c367c013d228df52f8332ae6744cf2..fadc4998deb9a3ee8948ceaa9b17869dca9bcc6b 100755 (executable)
@@ -1,13 +1,11 @@
 #!/usr/bin/make -f
 #!/usr/bin/make -f
-# -*- makefile -*-
-# Sample debian/rules that uses debhelper.
-# This file was originally written by Joey Hess and Craig Small.
-# As a special exception, when this file is copied by dh-make into a
-# dh-make output file, you may use that output file without restriction.
-# This special exception was added by Craig Small in version 0.37 of dh-make.
 
 # Uncomment this to turn on verbose mode.
 #export DH_VERBOSE=1
 
 %:
        dh $@ 
 
 # Uncomment this to turn on verbose mode.
 #export DH_VERBOSE=1
 
 %:
        dh $@ 
+
+override_dh_auto_install:
+       dh_auto_install
+       dh_installinit --name pvefw-logger
index 3a578cdb5ff462d63b491abc2a20d8848b584716..16806ee86ca8c30a4bdacb4cff2569186abf6459 100644 (file)
@@ -1,7 +1,7 @@
 
 PREFIX=/usr
 BINDIR=${PREFIX}/bin
 
 PREFIX=/usr
 BINDIR=${PREFIX}/bin
-SBINDIR=${PREFIX}/bin
+SBINDIR=${PREFIX}/sbin
 MANDIR=${PREFIX}/share/man
 DOCDIR=${PREFIX}/share/doc
 MAN1DIR=${MANDIR}/man1/
 MANDIR=${PREFIX}/share/man
 DOCDIR=${PREFIX}/share/doc
 MAN1DIR=${MANDIR}/man1/
@@ -10,12 +10,22 @@ PERLDIR=${PREFIX}/share/perl5
 LIB_SOURCES=                   \
        Firewall.pm
 
 LIB_SOURCES=                   \
        Firewall.pm
 
-all:
+all: pvefw-logger
+
+CPPFLAGS:=$(shell dpkg-buildflags --get CPPFLAGS)
+CFLAGS:=$(shell dpkg-buildflags --get CFLAGS)
+LDFLAGS:=$(shell dpkg-buildflags --get LDFLAGS)
+
+pvefw-logger: pvefw-logger.c
+       gcc -Wall -Werror pvefw-logger.c -o pvefw-logger -std=gnu99 \
+       $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) \
+       $(shell pkg-config libnetfilter_log glib-2.0 gthread-2.0 --libs --cflags)
 
 .PHONY: install
 
 .PHONY: install
-install:
+install: pvefw pvefw-logger
        install -d -m 0755 ${DESTDIR}/${SBINDIR}
        install -m 0755 pvefw ${DESTDIR}/${SBINDIR}
        install -d -m 0755 ${DESTDIR}/${SBINDIR}
        install -m 0755 pvefw ${DESTDIR}/${SBINDIR}
+       install -m 0755 --strip pvefw-logger ${DESTDIR}/${SBINDIR}
        install -d -m 0755 ${DESTDIR}${PERLDIR}/PVE
        for i in ${LIB_SOURCES}; do install -D -m 0644 PVE/$$i ${DESTDIR}${PERLDIR}/PVE/$$i; done
 
        install -d -m 0755 ${DESTDIR}${PERLDIR}/PVE
        for i in ${LIB_SOURCES}; do install -D -m 0644 PVE/$$i ${DESTDIR}${PERLDIR}/PVE/$$i; done
 
index 4ed91dcb416127b014ba2d9e3ddf97339430cce7..495be05987477159ca69fe45a53ac04ef6a8479d 100644 (file)
@@ -18,7 +18,7 @@ use PVE::Tools qw(run_command lock_file);
 
 use Data::Dumper;
 
 
 use Data::Dumper;
 
-# fixme: use ULOG instead of LOG?
+# fixme: remove loglevel settings? NFLOG does not have --loglevel
 
 my $nodename = PVE::INotify::nodename();
 
 
 my $nodename = PVE::INotify::nodename();
 
@@ -888,14 +888,14 @@ sub ruleset_add_chain_policy {
 
        ruleset_addrule($ruleset, $chain, "-j PVEFW-Drop");
 
 
        ruleset_addrule($ruleset, $chain, "-j PVEFW-Drop");
 
-       ruleset_addrule($ruleset, $chain, "-j LOG --log-prefix \"$chain-dropped: \" --log-level $loglevel")
+       ruleset_addrule($ruleset, $chain, "-j NFLOG --nflog-prefix \"$chain-dropped: \"")
            if defined($loglevel);
 
        ruleset_addrule($ruleset, $chain, "-j DROP");
     } elsif ($policy eq 'REJECT') {
        ruleset_addrule($ruleset, $chain, "-j PVEFW-Reject");
 
            if defined($loglevel);
 
        ruleset_addrule($ruleset, $chain, "-j DROP");
     } elsif ($policy eq 'REJECT') {
        ruleset_addrule($ruleset, $chain, "-j PVEFW-Reject");
 
-       ruleset_addrule($ruleset, $chain, "-j LOG --log-prefix \"$chain-reject: \" --log-level $loglevel")
+       ruleset_addrule($ruleset, $chain, "-j NFLOG --nflog-prefix \"$chain-reject: \"")
            if defined($loglevel);
 
        ruleset_addrule($ruleset, $chain, "-g PVEFW-reject");
            if defined($loglevel);
 
        ruleset_addrule($ruleset, $chain, "-g PVEFW-reject");
@@ -1558,7 +1558,7 @@ sub generate_std_chains {
     # same as shorewall smurflog.
     if (defined($loglevel)) {
        $pve_std_chains-> {'PVEFW-smurflog'} = [
     # same as shorewall smurflog.
     if (defined($loglevel)) {
        $pve_std_chains-> {'PVEFW-smurflog'} = [
-           "-j LOG --log-prefix \"smurfs-dropped: \" --log-level $loglevel",
+           "-j NFLOG --nflog-prefix \"smurfs-dropped: \"",
            "-j DROP",
            ];
     } else {
            "-j DROP",
            ];
     } else {
@@ -1569,7 +1569,8 @@ sub generate_std_chains {
     $loglevel = get_option_log_level($options, 'tcp_flags_log_level');
     if (defined($loglevel)) {
        $pve_std_chains-> {'PVEFW-logflags'} = [
     $loglevel = get_option_log_level($options, 'tcp_flags_log_level');
     if (defined($loglevel)) {
        $pve_std_chains-> {'PVEFW-logflags'} = [
-           "-j LOG --log-prefix \"logflags-dropped: \" --log-level $loglevel --log-ip-options",
+           # fixme: is this correctly logged by pvewf-logger? (ther is no --log-ip-options for NFLOG)
+           "-j NFLOG --nflog-prefix \"logflags-dropped: \"",
            "-j DROP",
            ];
     } else {
            "-j DROP",
            ];
     } else {
@@ -1748,8 +1749,8 @@ sub compile {
     # disable interbridge routing
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o vmbr+ -j PVEFW-Drop"); 
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i vmbr+ -j PVEFW-Drop");
     # disable interbridge routing
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o vmbr+ -j PVEFW-Drop"); 
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i vmbr+ -j PVEFW-Drop");
-    ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o vmbr+ -j LOG --log-prefix \"PVEFW-FORWARD-dropped \" --log-level $loglevel");  
-    ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i vmbr+ -j LOG --log-prefix \"PVEFW-FORWARD-dropped \" --log-level $loglevel");  
+    ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o vmbr+ -j NFLOG --nflog-prefix \"PVEFW-FORWARD-dropped \"");  
+    ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i vmbr+ -j NFLOG --nflog-prefix \"PVEFW-FORWARD-dropped \"");  
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o vmbr+ -j DROP");  
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i vmbr+ -j DROP");
 
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-o vmbr+ -j DROP");  
     ruleset_addrule($ruleset, "PVEFW-FORWARD", "-i vmbr+ -j DROP");
 
diff --git a/src/pvefw-logger.c b/src/pvefw-logger.c
new file mode 100644 (file)
index 0000000..db7a026
--- /dev/null
@@ -0,0 +1,677 @@
+/*
+
+  Copyright (C) 2014 Proxmox Server Solutions GmbH
+
+  This software is written by Proxmox Server Solutions GmbH <support@proxmox.com>
+
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU Affero General Public License as published by
+  the Free Software Foundation, either version 3 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU Affero General Public License for more details.
+
+  You should have received a copy of the GNU Affero General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+  Author: Dietmar Maurer <dietmar@proxmox.com>
+
+*/
+
+#define _GNU_SOURCE
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/types.h> 
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/file.h>
+#include <linux/netlink.h>
+#include <libnfnetlink/libnfnetlink.h>
+#include <libnetfilter_log/libnetfilter_log.h>
+#include <netinet/ip.h>
+#include <netinet/ip_icmp.h>
+#include <netinet/udp.h>
+#include <netinet/tcp.h>
+#include <netinet/if_ether.h>
+
+#include <glib.h>
+#include <glib-unix.h>
+
+static struct nflog_handle *logh = NULL;
+static struct nlif_handle *nlifh = NULL;
+
+#define LOGFILE "/var/log/pve-firewall.log"
+
+#define LOCKFILE "/var/lock/pvefw-logger.lck"
+#define PIDFILE "/var/run/pvefw-logger.pid"
+
+#define LQ_LEN 512
+#define LE_MAX (512 - 16) // try to fit into 512 bytes
+
+struct log_entry { 
+    guint32 len; // max LE_MAX chars
+    char buf[LE_MAX];
+};
+
+int outfd = -1;
+
+gboolean terminate_threads = FALSE;
+
+static gboolean write_pidfile(pid_t pid)
+{
+    gboolean res;
+
+    char *strpid = g_strdup_printf("%d\n", pid);
+    res = g_file_set_contents(PIDFILE, strpid, strlen(strpid), NULL);
+    g_free(strpid);
+
+    return res;
+}
+
+static GAsyncQueue *queue;
+
+ssize_t 
+safe_write(int fd, char *buf, size_t count)
+{
+  ssize_t n;
+
+  do {
+    n = write(fd, buf, count);
+  } while (n < 0 && errno == EINTR);
+
+  return n;
+}
+
+static gpointer
+log_writer_thread(gpointer data) 
+{
+    while (1) {
+        struct log_entry *le = (struct log_entry *)g_async_queue_timeout_pop(queue, 250000);
+        if (le == NULL) {
+            if (terminate_threads) {
+                return NULL;
+            }
+            continue;
+        }
+        int res = safe_write(outfd, le->buf, le->len);
+
+        g_free(le);
+
+        if (res < 0) {
+            // printf("write failed\n"); // fixme??
+        }
+    }
+
+    return NULL;
+}
+
+static int skipped_logs = 0;
+
+static void log_status_message(const char *fmt, ...);
+static void
+queue_log_entry(struct log_entry *le)
+{
+    gint len = g_async_queue_length(queue);
+
+    if (skipped_logs > 0) {
+        if (len >= (LQ_LEN - 1)) {
+            skipped_logs++;
+        } else {
+            int skip_tmp = skipped_logs; 
+            skipped_logs = 0; // clear before calling log_status_message()
+            log_status_message("skipped %d log entries (queue full)", skip_tmp);
+            g_async_queue_push(queue, le);
+        }
+    } else {
+        if (len >= LQ_LEN) {
+            skipped_logs++;
+        } else {
+            g_async_queue_push(queue, le);
+        }
+    }
+}
+
+
+#define LEPRINTF(format, ...) { if (le->len < LE_MAX) le->len += snprintf(le->buf + le->len, LE_MAX - le->len, format, ##__VA_ARGS__); }
+#define LEPRINTTIME(sec) { time_t tmp_sec = sec; if (le->len < (LE_MAX - 30)) le->len += strftime(le->buf + le->len, LE_MAX - le->len, "%d/%b/%Y:%H:%M:%S %z ", localtime(&tmp_sec)); }
+
+static void 
+log_status_message(const char *fmt, ...) 
+{
+    va_list ap;
+    va_start(ap, fmt);
+    
+    struct log_entry *le = g_new0(struct log_entry, 1);
+
+    LEPRINTTIME(time(NULL));
+
+    le->len += vsnprintf(le->buf + le->len, LE_MAX - le->len, fmt, ap);
+
+    LEPRINTF("\n");
+
+    queue_log_entry(le);
+}
+
+static int 
+print_tcp(struct log_entry *le, struct tcphdr *h, int payload_len)
+{
+    LEPRINTF("PROTO=TCP ");
+
+    if (payload_len < sizeof(struct tcphdr)) {
+        LEPRINTF("LEN=%d ", payload_len);
+        LEPRINTF("INVALID=LEN ");
+        return -1;
+    }
+
+    LEPRINTF("SPT=%u DPT=%u ", ntohs(h->source), ntohs(h->dest));
+    LEPRINTF("SEQ=%u ACK=%u ", ntohl(h->seq), ntohl(h->ack_seq));
+    LEPRINTF("WINDOW=%u ", ntohs(h->window));
+
+    if (h->urg) LEPRINTF("URG ");
+    if (h->ack) LEPRINTF("ACK ");
+    if (h->psh) LEPRINTF("PSH ");
+    if (h->rst) LEPRINTF("RST ");
+    if (h->syn) LEPRINTF("SYN ");
+    if (h->fin) LEPRINTF("FIN ");
+
+    if (h->urg) LEPRINTF("URGP=%u ",ntohs(h->urg_ptr));
+
+    return 0;
+}
+
+static int 
+print_udp(struct log_entry *le, struct udphdr *h, int payload_len)
+{
+    LEPRINTF("PROTO=UDP ");
+
+    if (payload_len < sizeof(struct udphdr)) {
+        LEPRINTF("LEN=%d ", payload_len);
+        LEPRINTF("INVALID=LEN ");
+        return -1;
+    }
+
+    LEPRINTF("SPT=%u DPT=%u LEN=%u", ntohs(h->source), ntohs(h->dest), ntohs(h->len));
+
+    return 0;
+}
+
+static int 
+print_icmp(struct log_entry *le, struct icmphdr *h, int payload_len)
+{
+    char tmp[INET_ADDRSTRLEN];
+    u_int32_t gateway;
+
+    LEPRINTF("PROTO=ICMP ");
+
+    if (payload_len < sizeof(struct icmphdr)) {
+        LEPRINTF("LEN=%d ", payload_len);
+        LEPRINTF("INVALID=LEN ");
+        return -1;
+    }
+
+    LEPRINTF("TYPE=%u CODE=%u ", h->type, h->code);
+
+    switch (h->type) {
+    case ICMP_ECHO:
+    case ICMP_ECHOREPLY:
+        LEPRINTF("ID=%u SEQ=%u ", ntohs(h->un.echo.id), ntohs(h->un.echo.sequence));
+        break;
+    case ICMP_PARAMETERPROB:
+        LEPRINTF("PARAMETER=%u ", ntohl(h->un.gateway) >> 24);
+        break;
+    case ICMP_REDIRECT:
+        gateway = ntohl(h->un.gateway);
+        inet_ntop(AF_INET, &gateway, tmp, sizeof(tmp));                
+        LEPRINTF("GATEWAY=%s ", tmp);
+        break;
+    case ICMP_DEST_UNREACH:
+        if (h->code == ICMP_FRAG_NEEDED) {
+            LEPRINTF("MTU=%u ", ntohs(h->un.frag.mtu));
+        }
+        break;
+    }
+
+    return 0;
+}
+
+/* Section 3.1.  SCTP Common Header Format */
+typedef struct sctphdr {
+       __be16 source;
+       __be16 dest;
+       __be32 vtag;
+       __be32 checksum;
+} __attribute__((packed)) sctp_sctphdr_t;
+
+static int 
+print_sctp(struct log_entry *le, struct sctphdr *h, int payload_len)
+{
+    LEPRINTF("PROTO=SCTP ");
+
+    if (payload_len < sizeof(struct sctphdr)) {
+        LEPRINTF("LEN=%d ", payload_len);
+        LEPRINTF("INVALID=LEN ");
+        return -1;
+    }
+
+    LEPRINTF("SPT=%u DPT=%u ", ntohs(h->source), ntohs(h->dest));
+
+    return 0;
+}
+
+static int 
+print_iphdr(struct log_entry *le, char * payload, int payload_len)
+{
+    if (payload_len < sizeof(struct iphdr)) {
+       LEPRINTF("LEN=%d ", payload_len);
+       LEPRINTF("INVALID=LEN ");
+       return -1;
+    }
+
+    struct iphdr *h = (struct iphdr *)payload;
+    if (payload_len <= (u_int32_t)(h->ihl * 4)) {
+        LEPRINTF("INVALID=IHL ");
+        return -1;
+    }
+
+    char tmp[INET_ADDRSTRLEN];
+   
+    inet_ntop(AF_INET, &h->saddr, tmp, sizeof(tmp));                
+    LEPRINTF("SRC=%s ", tmp);
+    inet_ntop(AF_INET, &h->daddr, tmp, sizeof(tmp));                
+    LEPRINTF("DST=%s ", tmp);
+
+    LEPRINTF("LEN=%u TOS=0x%02X PREC=0x%02X TTL=%u ID=%u ",
+             ntohs(h->tot_len),  h->tos & IPTOS_TOS_MASK,
+             h->tos & IPTOS_PREC_MASK, h->ttl, ntohs(h->id));
+                
+    short ip_off = ntohs(h->frag_off);
+    if (ip_off & IP_OFFMASK) 
+        LEPRINTF("FRAG=%u ", ip_off & IP_OFFMASK);
+
+    if (ip_off & IP_DF) LEPRINTF("DF ");
+    if (ip_off & IP_MF) LEPRINTF("MF ");
+
+    void *nexthdr = (u_int32_t *)h + h->ihl;
+    payload_len -= h->ihl * 4;
+
+    switch (h->protocol) {
+    case IPPROTO_TCP:
+        print_tcp(le, (struct tcphdr *)nexthdr, payload_len);
+        break;
+    case IPPROTO_UDP:
+        print_udp(le, (struct udphdr *)nexthdr, payload_len);
+        break;
+    case IPPROTO_ICMP:
+        print_icmp(le, (struct icmphdr *)nexthdr, payload_len);
+        break;
+    case IPPROTO_SCTP:
+        print_sctp(le, (struct sctphdr *)nexthdr, payload_len);
+        break;
+    case IPPROTO_AH:
+        LEPRINTF("PROTO=AH ");
+        break;
+    case IPPROTO_ESP:
+        LEPRINTF("PROTO=ESP ");
+        break;
+    case IPPROTO_IGMP:
+        LEPRINTF("PROTO=IGMP ");
+        break;
+     default:
+        LEPRINTF("PROTO=%u ", h->protocol);
+    }
+
+    return 0;
+}
+
+static int 
+print_ip6hdr(struct log_entry *le, char * payload, int payload_len)
+{
+    LEPRINTF("IPV6 logging not implemented ");
+
+    return 0;
+}
+
+// ebtables -I FORWARD --nflog --nflog-group 0
+static int 
+print_arp(struct log_entry *le, struct ether_arp *h, int payload_len)
+{
+    if (payload_len < sizeof(struct ether_arp)) {
+        LEPRINTF("LEN=%d ", payload_len);
+        LEPRINTF("INVALID=LEN ");
+        return -1;
+    }
+
+    LEPRINTF("SRC=%u.%u.%u.%u ", h->arp_spa[0], h->arp_spa[1],
+             h->arp_spa[2], h->arp_spa[3]);
+
+    LEPRINTF("DST=%u.%u.%u.%u ", h->arp_tpa[0], h->arp_tpa[1],
+             h->arp_tpa[2], h->arp_tpa[3]);
+
+    LEPRINTF("PROTO=ARP ");
+
+    unsigned short code = ntohs(h->arp_op);
+    switch (code) {
+    case ARPOP_REQUEST:
+        LEPRINTF("REQUEST ");
+        break;
+    case ARPOP_REPLY:
+        LEPRINTF("REPLY MAC=%02x:%02x:%02x:%02x:%02x:%02x ", 
+                 h->arp_sha[0], h->arp_sha[1], h->arp_sha[2], 
+                 h->arp_sha[3], h->arp_sha[4], h->arp_sha[5]);
+        break;
+    case ARPOP_NAK:
+        LEPRINTF("NAK ");
+        break;
+    default:
+        LEPRINTF("CODE=%u ", code);
+    }
+
+
+    // LEPRINTF("HTYPE=%u ", ntohs(h->arp_hrd));
+
+    // LEPRINTF("PTYPE=%u ", ntohs(h->arp_pro));
+
+    return 0;
+}
+
+
+static int print_pkt(struct log_entry *le, struct nflog_data *ldata, u_int8_t family)
+{
+    u_int32_t mark = nflog_get_nfmark(ldata);
+    u_int32_t indev = nflog_get_indev(ldata);
+    u_int32_t outdev = nflog_get_outdev(ldata);
+    u_int32_t physindev = nflog_get_physindev(ldata);
+    u_int32_t physoutdev = nflog_get_physoutdev(ldata);
+
+    char *prefix = nflog_get_prefix(ldata);
+    char *payload;
+    char devname[256];
+    
+    struct timeval ts;
+    nflog_get_timestamp(ldata, &ts);
+
+    LEPRINTTIME(ts.tv_sec);
+
+    //le->len += strftime(le->buf + le->len, LE_MAX - le->len, "%d/%b/%Y:%H:%M:%S %z ", localtime(&ts.tv_sec));
+
+    if (prefix != NULL) {
+        LEPRINTF("%s", prefix);
+    }
+    
+    if ((indev > 0) && (nlif_index2name(nlifh, indev, devname) != -1)) {
+        LEPRINTF("IN=%s ", devname); 
+    }
+
+    if ((outdev > 0) && (nlif_index2name(nlifh, outdev, devname) != -1)) {
+        LEPRINTF("OUT=%s ", devname);
+    }
+
+    if ((physindev > 0) && (nlif_index2name(nlifh, physindev, devname) != -1)) {
+        LEPRINTF("PHYSIN=%s ", devname);
+    }
+         
+    if ((physoutdev > 0) &&  (nlif_index2name(nlifh, physoutdev, devname) != -1)) {
+        LEPRINTF("PHYSOUT=%s ", devname);
+    }
+
+    int payload_len = nflog_get_payload(ldata, &payload);
+
+    int hwhdrlen = nflog_get_msg_packet_hwhdrlen(ldata);
+    if (hwhdrlen > 0) {
+        unsigned char *hwhdr = (unsigned char *)nflog_get_msg_packet_hwhdr(ldata);
+        if (hwhdr != NULL) {
+            int i;
+            LEPRINTF("MAC=");
+            for (i = 0; i < hwhdrlen; i++) {
+                LEPRINTF("%02x", hwhdr[i]);
+                if (i < (hwhdrlen -1 )) LEPRINTF(":");
+            }
+            LEPRINTF(" ");
+        }
+    }
+
+    u_int16_t hw_protocol = 0;
+    struct nfulnl_msg_packet_hdr *ph = NULL;
+
+    switch (family) {
+    case AF_INET: 
+        print_iphdr(le, payload, payload_len);
+        break;
+    case AF_INET6:
+        print_ip6hdr(le, payload, payload_len);
+        break;
+    case AF_BRIDGE:
+        ph = nflog_get_msg_packet_hdr(ldata);
+        if (ph) hw_protocol = ntohs(ph->hw_protocol);
+
+        switch (hw_protocol) {
+        case ETH_P_IP:
+            print_iphdr(le, payload, payload_len);
+            break;
+        case ETH_P_IPV6:
+             print_ip6hdr(le, payload, payload_len);
+            break;
+        case ETH_P_ARP:
+            print_arp(le, (struct ether_arp *)payload, payload_len);
+            break;
+        }
+        break;
+    }
+
+    if (mark) LEPRINTF("mark=%u ", mark);
+
+
+    return 0;
+
+}
+
+static int 
+nflog_cb(struct nflog_g_handle *gh, struct nfgenmsg *nfmsg,
+               struct nflog_data *nfa, void *data)
+{
+    struct log_entry *le = g_new0(struct log_entry, 1);
+    print_pkt(le, nfa, nfmsg->nfgen_family);
+
+    LEPRINTF("\n"); // add newline
+
+    queue_log_entry(le);
+
+    return 0;
+}
+
+static gboolean
+nflog_read_cb(GIOChannel *source,
+              GIOCondition condition,
+              gpointer data)
+{
+    int rv = 0;
+    gchar buf[8192];
+
+    int fd =  g_io_channel_unix_get_fd(source);
+   
+    if ((rv = recv(fd, buf, sizeof(buf), 0)) && rv >= 0) {
+         nflog_handle_packet(logh, buf, rv);
+    }
+
+    return TRUE;
+}
+
+static gboolean
+nlif_read_cb(GIOChannel *source,
+             GIOCondition condition,
+             gpointer data)
+{
+    nlif_catch(nlifh);
+    // fixme: report  errors
+    return TRUE; 
+}
+
+GMainLoop *main_loop;
+
+static gboolean
+terminate_request(gpointer data) 
+{
+    terminate_threads = TRUE;
+
+    log_status_message("received terminate request (signal)");
+
+    g_main_loop_quit(main_loop);
+    
+    return TRUE;
+}
+                
+
+int
+main(int argc, char *argv[])
+{
+    int lockfd = -1;
+    gboolean foreground = FALSE;
+    gboolean wrote_pidfile = FALSE;
+
+    g_thread_init(NULL);
+
+    if ((lockfd = open(LOCKFILE, O_RDWR|O_CREAT|O_APPEND, 0644)) == -1) {
+        fprintf(stderr, "unable to create lock '%s': %s", LOCKFILE, strerror (errno));
+        exit(-1);
+    }
+
+    for (int i = 10; i >= 0; i--) {
+        if (flock(lockfd, LOCK_EX|LOCK_NB) != 0) {
+            if (!i) {
+                fprintf(stderr, "unable to aquire lock '%s': %s", LOCKFILE, strerror (errno));
+                exit(-1);
+            }
+            if (i == 10)
+                fprintf(stderr, "unable to aquire lock '%s' - trying again.\n", LOCKFILE);
+            
+            sleep(1);
+        }
+    }
+
+    if ((outfd = open(LOGFILE, O_WRONLY|O_CREAT|O_APPEND, 0644)) == -1) {
+        fprintf(stderr, "unable to open file '%s': %s", LOGFILE, strerror (errno));
+        exit(-1);
+    }
+
+    if ((logh = nflog_open())  == NULL) {
+        fprintf(stderr, "unable to open nflog\n");
+        exit(-1);
+    }
+
+    if (!nflog_bind_pf(logh, AF_INET) <= 0) {
+        fprintf(stderr, "nflog_bind_pf AF_INET failed\n");
+        exit(-1);
+    }
+
+#if 0
+    if (!nflog_bind_pf(logh, AF_INET6) <= 0) {
+        fprintf(stderr, "nflog_bind_pf AF_INET6 failed\n");
+        exit(-1);
+    }
+#endif
+    
+    if (!nflog_bind_pf(logh, AF_BRIDGE) <= 0) {
+        fprintf(stderr, "nflog_bind_pf AF_BRIDGE failed\n");
+        exit(-1);
+    }
+
+    struct nflog_g_handle *qh = nflog_bind_group(logh, 0);
+    if (!qh) {
+        fprintf(stderr, "no handle for group 1\n");
+        exit(-1);
+    }
+    if (nflog_set_mode(qh, NFULNL_COPY_PACKET, 0xffff) < 0) {
+        fprintf(stderr, "can't set packet copy mode\n");
+        exit(-1);
+    }
+
+    if ((nlifh = nlif_open()) == NULL) {
+        fprintf(stderr, "unable to open netlink interface handle\n");
+        exit(-1);
+    }
+
+    if (!foreground) {
+        pid_t cpid = fork();
+
+        if (cpid == -1) {
+            fprintf(stderr, "failed to daemonize program - %s\n", strerror (errno));
+            exit(-1);
+        } else if (cpid) {
+            write_pidfile(cpid);
+            _exit(0);
+        } else {
+            int nullfd;
+
+            if (chroot("/") != 0) fprintf(stderr, "chroot '/' failed - %s\n", strerror (errno));
+
+            if ((nullfd = open("/dev/null", O_RDWR, 0)) != -1) {
+                dup2(nullfd, 0);
+                dup2(nullfd, 1);
+                dup2(nullfd, 2);
+                if (nullfd > 2)
+                    close (nullfd);
+            }
+
+            setsid();
+        }
+    } else {
+        write_pidfile(getpid());
+    }
+
+    wrote_pidfile = TRUE;
+
+    nflog_callback_register(qh, &nflog_cb, logh);
+
+    queue = g_async_queue_new_full(g_free);
+
+    log_status_message("starting pvefw logger");
+
+    nlif_query(nlifh);
+
+    GIOChannel *nlif_ch = g_io_channel_unix_new(nlif_fd(nlifh));
+
+    g_io_add_watch(nlif_ch, G_IO_IN, nlif_read_cb, NULL);
+
+    int logfd = nflog_fd(logh);
+    GIOChannel *nflog_ch = g_io_channel_unix_new(logfd);
+
+    g_io_add_watch(nflog_ch, G_IO_IN, nflog_read_cb, NULL);
+
+    GThread *wthread = g_thread_new("log_writer_thread", log_writer_thread, NULL);
+    
+    main_loop = g_main_loop_new(NULL, TRUE);
+    
+    g_unix_signal_add(SIGINT, terminate_request, NULL);
+    g_unix_signal_add(SIGTERM, terminate_request, NULL);
+    
+    g_main_loop_run(main_loop);
+
+    log_status_message("stopping pvefw logger");
+
+    g_thread_join(wthread);
+
+    close(outfd);
+
+    nflog_close(logh);
+
+    if (wrote_pidfile)
+        unlink(PIDFILE);
+
+    exit(0);
+}