From: Dietmar Maurer Date: Thu, 13 Mar 2014 12:08:47 +0000 (+0100) Subject: add simple nflog daemon X-Git-Url: https://git.proxmox.com/?p=pve-firewall.git;a=commitdiff_plain;h=ba0b3a0a1eae4d40ea2cdeeea33b47ad150f4ad1 add simple nflog daemon --- diff --git a/Makefile b/Makefile index 7180795..79fe413 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ RELEASE=3.3 VERSION=1.0 -PKGREL=1 +PKGREL=2 PACKAGE=pve-firewall @@ -13,7 +13,7 @@ DOCDIR=${PREFIX}/share/doc MAN1DIR=${MANDIR}/man1/ PERLDIR=${PREFIX}/share/perl5 -ARCH=all +ARCH=amd64 GITVERSION:=$(shell cat .git/refs/heads/master) DEB=${PACKAGE}_${VERSION}-${PKGREL}_${ARCH}.deb @@ -26,7 +26,7 @@ dinstall: deb .PHONY: deb -deb ${DEB}: +deb ${DEB}: 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 - diff --git a/debian/changelog b/debian/changelog index 0c99eaf..597ed0a 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +pve-firewall (1.0-2) unstable; urgency=low + + * add experimental nflog logging daemon + + -- Proxmox Support Team Thu, 13 Mar 2014 08:27:01 +0100 + pve-firewall (1.0-1) unstable; urgency=low * initial package diff --git a/debian/control b/debian/control index 1387783..42e3f7b 100644 --- a/debian/control +++ b/debian/control @@ -2,11 +2,12 @@ Source: pve-firewall Section: admin Priority: extra Maintainer: Proxmox Support Team -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 -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. diff --git a/debian/install b/debian/install index 2ab287d..29fa088 100644 --- a/debian/install +++ b/debian/install @@ -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 index 0000000..13ee0eb --- /dev/null +++ b/debian/pve-firewall.logrotate @@ -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 +} diff --git a/debian/rules b/debian/rules index b760bee..fadc499 100755 --- a/debian/rules +++ b/debian/rules @@ -1,13 +1,11 @@ #!/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 $@ + +override_dh_auto_install: + dh_auto_install + dh_installinit --name pvefw-logger diff --git a/src/Makefile b/src/Makefile index 3a578cd..16806ee 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,7 @@ PREFIX=/usr BINDIR=${PREFIX}/bin -SBINDIR=${PREFIX}/bin +SBINDIR=${PREFIX}/sbin MANDIR=${PREFIX}/share/man DOCDIR=${PREFIX}/share/doc MAN1DIR=${MANDIR}/man1/ @@ -10,12 +10,22 @@ PERLDIR=${PREFIX}/share/perl5 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 -install: +install: pvefw pvefw-logger 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 diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm index 4ed91dc..495be05 100644 --- a/src/PVE/Firewall.pm +++ b/src/PVE/Firewall.pm @@ -18,7 +18,7 @@ use PVE::Tools qw(run_command lock_file); use Data::Dumper; -# fixme: use ULOG instead of LOG? +# fixme: remove loglevel settings? NFLOG does not have --loglevel 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 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"); - 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"); @@ -1558,7 +1558,7 @@ sub generate_std_chains { # 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 { @@ -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'} = [ - "-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 { @@ -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"); - 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"); diff --git a/src/pvefw-logger.c b/src/pvefw-logger.c new file mode 100644 index 0000000..db7a026 --- /dev/null +++ b/src/pvefw-logger.c @@ -0,0 +1,677 @@ +/* + + Copyright (C) 2014 Proxmox Server Solutions GmbH + + This software is written by Proxmox Server Solutions GmbH + + 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 . + + Author: Dietmar Maurer + +*/ + +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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); +}