aboutsummaryrefslogtreecommitdiffstats
path: root/testing/ipt-netflow
diff options
context:
space:
mode:
Diffstat (limited to 'testing/ipt-netflow')
-rw-r--r--testing/ipt-netflow/APKBUILD49
-rw-r--r--testing/ipt-netflow/git.patch4308
-rw-r--r--testing/ipt-netflow/kernel-vs-userspace.patch56
3 files changed, 4413 insertions, 0 deletions
diff --git a/testing/ipt-netflow/APKBUILD b/testing/ipt-netflow/APKBUILD
new file mode 100644
index 0000000000..2adb8e03ae
--- /dev/null
+++ b/testing/ipt-netflow/APKBUILD
@@ -0,0 +1,49 @@
+# Contributor: Rush Future <rush.zlo@gmail.com>
+# Maintainer: Natanael Copa <ncopa@alpinelinux.org>
+pkgname=ipt-netflow
+pkgver=1.8_git20140103
+_ver=${pkgver%_git*}
+pkgrel=0
+pkgdesc="Kernel netflow sensor"
+url="http://ipt-netflow.sourceforge.net."
+arch="all"
+license="GPL3+"
+makedepends="iptables-dev bash"
+source="$pkgname-$_ver.tar.gz::https://github.com/aabc/ipt-netflow/archive/v$_ver.tar.gz
+ git.patch
+ kernel-vs-userspace.patch
+ "
+
+_builddir="$srcdir/$pkgname-$_ver"
+prepare() {
+ cd "$_builddir"
+ for i in $source; do
+ case $i in
+ *.patch) msg $i; patch -p1 -i "$srcdir"/$i || return 1;;
+ esac
+ done
+}
+
+build() {
+ cd "$_builddir"
+ ./configure \
+ --ipt-inc=/usr/include \
+ --disable-kernel \
+ || return 1
+ make libipt_NETFLOW.so || return 1
+}
+
+package() {
+ cd "$_builddir"
+ make linstall DESTDIR=$pkgdir || return 1
+}
+
+md5sums="922a7d9dd17f28a2c3551a1c5e9849eb ipt-netflow-1.8.tar.gz
+77875a5979589bca8c49a8331e7cc22b git.patch
+59adcee5cd4cec4e09f432f567ff8243 kernel-vs-userspace.patch"
+sha256sums="eefe766eda6d4d918e4cc919b5d5acd2b681a258246017ab7948b80f0cb95112 ipt-netflow-1.8.tar.gz
+75ea3afe9532699d4fd1bef7f198dfaa97b3a310234a86b48371522ae0704a60 git.patch
+a897d88212b56c3d06645f8fc9fe7ce5b7accfa1d45b96917b3ba747605a82bc kernel-vs-userspace.patch"
+sha512sums="c413515deeac1d604e36a53b39edcf340d6c3f78c29e53979fede8aa013e324ada4622b571ac5270f7495ab6982096cd2bd9e9283c9cc2cc7360aa5c3954792d ipt-netflow-1.8.tar.gz
+3fade2ceb00ddae15dd6b1eacebc36480efe0c5c2d050e050e0aef37382ad72ca454f4d23080b7263ab2f078f72db4572e2514d3faca113d8e437437f7c3afcf git.patch
+dfc37ccc010559994a94290aee99dbc75c47732cbc06492366bff081bafced7bccc5144b5119dc8f0ad8dea1392684ef7f592d15131da2fa1227516dc7d8d3ce kernel-vs-userspace.patch"
diff --git a/testing/ipt-netflow/git.patch b/testing/ipt-netflow/git.patch
new file mode 100644
index 0000000000..faa49ffdc7
--- /dev/null
+++ b/testing/ipt-netflow/git.patch
@@ -0,0 +1,4308 @@
+diff --git a/Makefile.in b/Makefile.in
+index 30ebbfe..0a82c67 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -1,43 +1,73 @@
+-#
++# Edit Makefile.in and run ./configure
+
+ KVERSION = @KVERSION@
+ KDIR = @KDIR@
++KINSTDIR = $(shell dirname @KDIR@)
+ IPTABLES_CFLAGS = @IPTABLES_CFLAGS@
+ IPTABLES_MODULES = @IPTABLES_MODULES@
++DEPMOD = depmod -a
+
++# https://www.kernel.org/doc/Documentation/kbuild/modules.txt
++# https://www.kernel.org/doc/Documentation/kbuild/makefiles.txt
+ obj-m = ipt_NETFLOW.o
+
+-ipt_NETFLOW.ko: ipt_NETFLOW.c ipt_NETFLOW.h
++all: ipt_NETFLOW.ko libipt_NETFLOW.so libip6t_NETFLOW.so
++ipt_NETFLOW.ko: version.h ipt_NETFLOW.c ipt_NETFLOW.h Makefile
+ @echo Compiling for kernel $(KVERSION)
+ make -C $(KDIR) M=$(CURDIR) modules
+-all: ipt_NETFLOW.ko libipt_NETFLOW.so
+-minstall:
+- make -C $(KDIR) M=$(CURDIR) modules_install
++ @touch $@
++sparse: | version.h ipt_NETFLOW.c ipt_NETFLOW.h Makefile
++ @rm -f ipt_NETFLOW.ko ipt_NETFLOW.o
++ @echo Compiling for kernel $(KVERSION)
++ make -C $(KDIR) M=$(CURDIR) modules C=1
++ @touch ipt_NETFLOW.ko
++minstall: | ipt_NETFLOW.ko
++ make -C $(KDIR) M=$(CURDIR) modules_install INSTALL_MOD_PATH=$(DESTDIR)
++ $(DEPMOD)
+ mclean:
+ make -C $(KDIR) M=$(CURDIR) clean
+ lclean:
+ -rm -f *.so *_sh.o
+ clean: mclean lclean
+- -rm -f *.so *.o modules.order
++ -rm -f *.so *.o modules.order version.h
++
++%_sh.o: libipt_NETFLOW.c
++ gcc -O2 -Wall -Wunused $(IPTABLES_CFLAGS) -fPIC -o $@ -c libipt_NETFLOW.c
++
++%.so: %_sh.o
++ gcc -shared -o $@ $<
+
+-libipt_NETFLOW.so: libipt_NETFLOW.c
+- gcc -O2 -Wall -Wunused -I$(KDIR)/include $(IPTABLES_CFLAGS) -fPIC -o libipt_NETFLOW_sh.o -c libipt_NETFLOW.c
+- gcc -shared -o libipt_NETFLOW.so libipt_NETFLOW_sh.o
++version.h: ipt_NETFLOW.c ipt_NETFLOW.h Makefile
++ @if [ -d .git ] && type git >/dev/null 2>&1; then \
++ echo "#define GITVERSION \"`git describe --dirty`\""; \
++ fi > version.h
+
+-linstall: ipt_NETFLOW.ko libipt_NETFLOW.so
+- cp -a libipt_NETFLOW.so $(IPTABLES_MODULES)
++linstall: | libipt_NETFLOW.so libip6t_NETFLOW.so
++ install -D libipt_NETFLOW.so $(DESTDIR)$(IPTABLES_MODULES)/libipt_NETFLOW.so
++ install -D libip6t_NETFLOW.so $(DESTDIR)$(IPTABLES_MODULES)/libip6t_NETFLOW.so
+
+ install: minstall linstall
+
++uninstall:
++ -rm -f $(DESTDIR)$(IPTABLES_MODULES)/libipt_NETFLOW.so
++ -rm -f $(DESTDIR)$(IPTABLES_MODULES)/libip6t_NETFLOW.so
++ -rm -f $(DESTDIR)$(KINSTDIR)/extra/ipt_NETFLOW.ko
++
+ Makefile: Makefile.in configure
+ ./configure --make
+
+ load: all
+- insmod ipt_NETFLOW.ko active_timeout=5
+- iptables -A OUTPUT -d 0/0 -j NETFLOW
+- iptables -A INPUT -d 0/0 -j NETFLOW
++ -insmod ipt_NETFLOW.ko active_timeout=5 protocol=9
++ -iptables -I OUTPUT -j NETFLOW
++ -iptables -I INPUT -j NETFLOW
++ -ip6tables -I OUTPUT -j NETFLOW
++ -ip6tables -I INPUT -j NETFLOW
+
+ unload:
+- iptables -D OUTPUT -d 0/0 -j NETFLOW
+- iptables -D INPUT -d 0/0 -j NETFLOW
+- rmmod ipt_NETFLOW.ko
++ -iptables -D OUTPUT -j NETFLOW
++ -iptables -D INPUT -j NETFLOW
++ -ip6tables -D OUTPUT -j NETFLOW
++ -ip6tables -D INPUT -j NETFLOW
++ -rmmod ipt_NETFLOW.ko
++
++reload: unload load
+diff --git a/README b/README
+index 213f02c..56a4fde 100644
+--- a/README
++++ b/README
+@@ -1,10 +1,17 @@
+-ipt_NETFLOW linux 2.6 kernel module by <abc@telekom.ru> -- 11 Feb 2008
++ipt_NETFLOW linux 2.6.x-3.x kernel module by <abc@telekom.ru> -- 2008-2013.
++
++ High performance NetFlow v5, v9, IPFIX flow data export module for Linux
++ kernel. Supporting IPv4 and IPv6. Created to be useful for highly loaded
++ linux router. It should be used as iptables target. Also can export NAT
++ translation events using NetFlow Event Logging (NEL) for v9, IPFIX, or
++ specially crafted v5 flows.
++
+
+ ============================
+ = OBTAINING LATEST VERSION =
+ ============================
+
+- $ git clone git://ipt-netflow.git.sourceforge.net/gitroot/ipt-netflow/ipt-netflow
++ $ git clone git://git.code.sf.net/p/ipt-netflow/code ipt-netflow
+ $ cd ipt-netflow
+
+
+@@ -12,94 +19,220 @@ ipt_NETFLOW linux 2.6 kernel module by <abc@telekom.ru> -- 11 Feb 2008
+ = INSTALLATION =
+ ================
+
+-1. Besides kernel you will need iptables/netfilter source matching your
+- installation or just fresh install from there: ftp://ftp.netfilter.org/pub/iptables/snapshot/
+- I have this: ftp://ftp.netfilter.org/pub/iptables/snapshot/iptables-1.3.7-20070329.tar.bz2
+- Unpack it somewhere and build with make.
++ Four easy steps.
++
++** 1. Prepare Kernel source
++
++ If you have package system install kernel-devel package, otherwise install
++ raw kernel source from http://kernel.org matching _exactly_ version of your
++ installed kernel.
++
++ a) What to do for Centos:
++
++ ~# yum install kernel-devel
++
++ b) What to do for Debian:
++
++ ~# apt-get install module-assistant
++ ~# m-a prepare
++
++ c) Otherwise, if you downloaded raw kernel sources don't forget to create
++ .config by copying it from your distribution's kernel. Its copy could reside
++ in /boot or sometimes in /proc, examples:
++
++ kernel-src-dir/# cp /boot/config-`uname -r` .config
++ or
++ kernel-src-dir/# zcat /proc/config.gz > .config
++
++ Assuming you unpacked kernel source into `kernel-src-dir/' directory.
++ Then run:
++
++ kernel-src-dir/# make oldconfig
++
++ After that you'll need to prepare kernel for modules build:
++
++ kernel-src-dir/# make prepare modules_prepare
++
++ Note: Don't try to `make prepare' in Centos kernel-devel package directory
++ (which is usually something like /usr/src/kernels/2.6.32-431.el6.x86_64)
++ as this is wrong and meaningless.
++
++** 2. Prepare Iptables
++
++ Before this step it also would be useful to install pkg-config if don't
++ already have.
++
++ If you have package system just install iptables-devel (or iptables-dev)
++ package, otherwise install iptables source matching version of your
++ installation from ftp://ftp.netfilter.org/pub/iptables/
++
++ a) What to do for Centos:
++
++ # yum install iptables-devel
++
++ b) What to do for Debian:
++
++ # apt-get install iptables-dev pkg-config
+
+-2. Run ./configure script and it will create Makefile
++ c) Otherwise, for raw iptables source build it and make install.
+
+-3. make all install; depmod
+- This will install kernel module and iptable specific library.
++** 3. Now, to actually build the module run:
+
+-Troubleshooting:
+- 1) Sometimes you will want to add CC=gcc-3 to make command.
+- Example: make CC=gcc-3.3
++ ~/ipt-netflow# ./configure
++ ~/ipt-netflow# make all install
++ ~/ipt-netflow# depmod
+
+- 2) Compile module with actual kernel source compiled.
+- I.e. first compile kernel and boot into it, and then compile module.
++ This will install kernel module and iptables specific library.
+
+- 3) For autoloading module after reboot: set net.netflow.destination (or load
+- module, if idestination set on load) after interfaces are up. Becasue module
+- needs exporting interface (usually lo) to establish export connection.
++ Troubleshooting:
+
+-4. After this point you should be able to load module
+- and use -j NETFLOW target in your iptables. See next section.
++ a) Sometimes you will want to add CC=gcc-3 to make command.
++ Example: make CC=gcc-3.3
++
++ b) Compile module with actual kernel source compiled.
++ I.e. first compile kernel and boot into it, and then compile module.
++ If you are using kernel-devel package check that its version matches
++ your kernel package.
++
++ c) If you have sources in non-standard places or configure isn't able to
++ find something run ./configure --help to see how to specify paths manually.
++
++** 4. After this point you should be able to load module and
++ use -j NETFLOW target in your iptables. See next section.
+
+
+ ===========
+ = RUNNING =
+ ===========
+
+-1. You can load module by insmod like this:
+- # insmod ipt_NETFLOW.ko destination=127.0.0.1:2055 debug=1
++1. You can load module directly by insmod like this:
++
++ # insmod ipt_NETFLOW.ko destination=127.0.0.1:2055 debug=1
+
+ Or if properly installed (make install; depmod) by this:
+- # modprobe ipt_NETFLOW destination=127.0.0.1:2055
++
++ # modprobe ipt_NETFLOW destination=127.0.0.1:2055
+
+ See, you may add options in insmod/modprobe command line, or add
+- them in /etc/ to modules.conf or modprobe.conf like thus:
+- options ipt_NETFLOW destination=127.0.0.1:2055
++ them in /etc/modprobe.conf or /etc/modprobe.d/ipt_NETFLOW.conf
++ like thus:
++
++ options ipt_NETFLOW destination=127.0.0.1:2055 protocol=9 natevents=1
+
+ 2. Statistics is in /proc/net/stat/ipt_netflow
+- To view slab statistics: grep ipt_netflow /proc/slabinfo
++ To view boring slab statistics: grep ipt_netflow /proc/slabinfo
+
+ 3. You can view parameters and control them via sysctl, example:
+- # sysctl -w net.netflow.hashsize=32768
+
+-4. Example of directing all traffic into module:
+- # iptables -A FORWARD -j NETFLOW
+- # iptables -A INPUT -j NETFLOW
+- # iptables -A OUTPUT -j NETFLOW
++ # sysctl net.netflow
++ # sysctl net.netflow.hashsize=32768
++
++ Note: For after-reboot configuration I recommend to store module parameters
++ in modprobe configs instead of storing them in /etc/sysctl.conf, as it's
++ less clear when init process will apply sysctl.conf, before of after
++ module's load.
++
++4. Example of directing all IPv4 traffic into the module:
++
++ # iptables -I FORWARD -j NETFLOW
++ # iptables -I INPUT -j NETFLOW
++ # iptables -I OUTPUT -j NETFLOW
++
++ Note: It is preferable (because easier to understand) to _insert_
++ NETFLOW target at the top of the chain, otherwise not all traffic may
++ reach NETFLOW if your iptables configuration is complicated and some
++ other rule inadvertently consume the traffic (dropping or acepting before
++ NETFLOW is reached). It's always good to test your configuration.
++ Use iptables -L -nvx to check pkts/bytes counters on the rules.
++
++5. If you want to account IPv6 traffic you should use protocol 9 or 10.
++ Example of directing all IPv6 traffic into the module:
+
++ # sysctl net.netflow.protocol=10
++ # ip6tables -I FORWARD -j NETFLOW
++ # ip6tables -I INPUT -j NETFLOW
++ # ip6tables -I OUTPUT -j NETFLOW
++
++ Note: First enable right version of protocol and after that add ip6tables
++ rules, otherwise you will get errors in dmesg.
++
++6. If you want to account NAT events (NEL):
++
++ # sysctl net.netflow.natevents=1
++
++ Note that natevents feature is completely independent from traffic accounting
++ (it's using so called conntrack events), thus you don't need to set or change
++ any iptables rules to use that. You may need to enable kernel config option
++ CONFIG_NF_CONNTRACK_EVENTS though (if it isn't already enabled).
++ For details on how they are exported for different protocol versions see
++ below.
+
+ ===========
+ = OPTIONS =
+ ===========
+
++ protocol=5
++ - what version of NetFlow protocol to use. Default is 5.
++ You can choose from 5, 9, or 10 (where 10 is IPFIX). If you plan
++ to account IPv6 traffic you should use protocol 9 or 10 (IPFIX),
++ because NetFlow v5 isn't compatible with IPv6.
++
+ destination=127.0.0.1:2055
+ - where to export netflow, to this ip address
+ You will see this connection in netstat like this:
+ udp 0 0 127.0.0.1:32772 127.0.0.1:2055 ESTABLISHED
+
+ destination=127.0.0.1:2055,192.0.0.1:2055
+- - mirror flows to two (can be more) addresses,
+- separate addresses with comma.
++ - mirror flows to two (can be more) addresses, separate addresses
++ with comma.
++
++ natevents=1
++ - Collect and send NAT translation events as NetFlow Event Logging (NEL)
++ for NetFlow v9/IPFIX, or as dummy flows compatible with NetFlow v5.
++ Default is 0 (don't send).
++
++ For NetFlow v5 protocol meaning of fields in dummy flows are such:
++ Src IP, Src Port is Pre-nat source address.
++ Dst IP, Dst Port is Post-nat destination address.
++ - These two fields made equal to data flows catched in FORWARD chain.
++ Nexthop, Src AS is Post-nat source address for SNAT. Or,
++ Nexthop, Dst AS is Pre-nat destination address for DNAT.
++ TCP Flags is SYN+SCK for start event, RST+FIN for stop event.
++ Pkt/Traffic size is 0 (zero), so it won't interfere with accounting.
+
+ inactive_timeout=15
+ - export flow after it's inactive 15 seconds. Default value is 15.
+
+ active_timeout=1800
+- - export flow after it's active 1800 seconds (30 minutes). Default value is 1800.
++ - export flow after it's active 1800 seconds (30 minutes). Default valuae
++ is 1800.
++
++ refresh-rate=20
++ - for NetFlow v9 and IPFIX it's rate how frequently to re-send templates
++ (per packets). You probably don't need to change default (which is 20).
++
++ timeout-rate=30
++ - for NetFlow v9 and IPFIX it's rate when to re-send old templates (in
++ minutes). No need to change it.
+
+ debug=0
+ - debug level (none).
+
+ sndbuf=number
+- - size of output socket buffer in bytes. Recommend you to put
++ - size of output socket buffer in bytes. I recommend you to put
+ higher value if you experience netflow packet drops (can be
+ seen in statistics as 'sock: fail' number.)
+ Default value is system default.
+
+ hashsize=number
+ - Hash table bucket size. Used for performance tuning.
+- Abstractly speaking, it should be two times bigger than flows
++ Abstractly speaking, it should be minimum two times bigger than flows
+ you usually have, but not need to.
+ Default is system memory dependent small enough value.
+
+ maxflows=2000000
+- - Maximum number of flows to account. It's here to prevent DOS attacks. After
+- this limit reached new flows will not be accounted. Default is
++ - Maximum number of flows to account. It's here to prevent DOS attacks.
++ After this limit reached new flows will not be accounted. Default is
+ 2000000, zero is unlimited.
+
+ aggregation=string..
+@@ -130,14 +263,15 @@ Troubleshooting:
+ = HOW TO READ STAT =
+ ====================
+
+- Statistics is your friend to fine tune and understand netflow module performance.
++ Statistics is your friend to fine tune and understand netflow module
++ performance.
+
+ To see stat:
+ # cat /proc/net/stat/ipt_netflow
+
+ How to interpret the data:
+
+-> Flows: active 5187 (peak 83905 reached 0d0h1m ago, maxflows 2000000), mem 283K
++> Flows: active 5187 (peak 83905 reached 0d0h1m ago, maxflows 2000000), mem 283K, worker delay 100/1000.
+
+ active X: currently active flows in memory cache.
+ - for optimum CPU performance it is recommended to set hash table size to
+@@ -146,8 +280,9 @@ Troubleshooting:
+ mem XK: how much kilobytes of memory currently taken by active flows.
+ - one active flow taking 56 bytes of memory.
+ - there is system limit on cache size too.
++ worker delay X/HZ: how frequently exporter scan flows table per second.
+
+-> Hash: size 8192 (mem 32K), metric 1.0, 1.0, 1.0, 1.0. MemTraf: 1420 pkt, 364 K (pdu 0, 0).
++> Hash: size 8192 (mem 32K), metric 1.00, [1.00, 1.00, 1.00]. MemTraf: 1420 pkt, 364 K (pdu 0, 0).
+
+ Hash: size X: current hash size/limit.
+ - you can control this by sysctl net.netflow.hashsize variable.
+@@ -156,18 +291,22 @@ Troubleshooting:
+ - optimal value is twice of average of active flows.
+ mem XK: how much memory occupied by hash table.
+ - hash table is fixed size by nature, taking 4 bytes per entry.
+- metric X, X, X, X: how optimal is your hash table being used.
++ metric X, [X, X, X]: how optimal is your hash table being used.
+ - lesser value mean more optimal hash table use, min is 1.0.
+- - this is moving average (EWMA) of hash table access divided
+- by match rate (searches / matches) for 4sec, and 1, 5, 15 minutes.
+- Sort of hash table load average.
++ - last three numbers in squares is moving average (EWMA) of hash table
++ access divided by match rate (searches / matches) for 4sec, and 1, 5, and
++ 15 minutes. Sort of hash table load average. First value is instantaneous.
++ You can try to increase hashsize if averages more than 1 (increase
++ certainly if >= 2).
+ MemTraf: X pkt, X K: how much traffic accounted for flows that are in memory.
+ - these flows that are residing in internal hash table.
+ pdu X, X: how much traffic in flows preparing to be exported.
+ - it is included already in aforementioned MemTraf total.
+
+-> Timeout: active 1800, inactive 15. Maxflows 2000000
++> Protocol version 10 (ipfix), refresh-rate 20, timeout-rate 30, (templates 2, active 2). Timeouts: active 5, inactive 15. Maxflows 2000000
+
++ Protocol version currently in use. Refresh-rate and timeout-rate
++ for v9 and IPFIX. Total templates generated and currently active.
+ Timeout: active X: how much seconds to wait before exporting active flow.
+ - same as sysctl net.netflow.active_timeout variable.
+ inactive X: how much seconds to wait before exporting inactive flow.
+@@ -180,20 +319,22 @@ Troubleshooting:
+
+ - Module throughput values for 1 second, 1 minute, and 5 minutes.
+
+-> cpu# stat: <search found new, trunc frag alloc maxflows>, sock: <ok fail cberr, bytes>, traffic: <pkt, bytes>, drop: <pkt, bytes>
+-> cpu0 stat: 980540 10473 180600, 0 0 0 0, sock: 4983 928 0, 7124 K, traffic: 188765, 14 MB, drop: 27863, 1142 K
++> cpu# stat: <search found new [metric], trunc frag alloc maxflows>, sock: <ok fail cberr, bytes>, traffic: <pkt, bytes>, drop: <pkt, bytes>
++> cpu0 stat: 980540 10473 180600 [1.03], 0 0 0 0, sock: 4983 928 0, 7124 K, traffic: 188765, 14 MB, drop: 27863, 1142 K
+
+ cpu#: this is Total and per CPU statistics for:
+ stat: <search found new, trunc frag alloc maxflows>: internal stat for:
+ search found new: hash table searched, found, and not found counters.
+- trunc: how much truncated packets is ignored
++ [metric]: average hash metric since module load.
++ trunc: how much truncated packets are ignored
+ - these are that possible don't have valid IP header.
+ - accounted in drop packets counter but not in drop bytes.
+ frag: how much fragmented packets have seen.
+ - kernel always defragments INPUT/OUTPUT chains for us.
+ - these packets are not ignored but not reassembled either, so:
+- - if there is no enough data in fragment (ex. tcp ports) it is considered zero.
+- alloc: how much cache memory allocations is failed.
++ - if there is no enough data in fragment (ex. tcp ports) it is considered
++ zero.
++ alloc: how much cache memory allocations are failed.
+ - packets ignored and accounted in drop stat.
+ - probably increase system memory if this ever happen.
+ maxflows: how much packets ignored on maxflows (maximum active flows reached).
+@@ -203,7 +344,8 @@ Troubleshooting:
+ sock: <ok fail cberr, bytes>: table of exporting stats for:
+ ok: how much Netflow PDUs are exported (i.e. UDP packets sent by module).
+ fail: how much socket errors (i.e. packets failed to be sent).
+- - packets dropped and their internal statistics cumulatively accounted in drop stat.
++ - packets dropped and their internal statistics cumulatively accounted in
++ drop stat.
+ cberr: how much connection refused ICMP errors we got from export target.
+ - probably you not launched collector software on destination,
+ - or specified wrong destination address.
+@@ -225,20 +367,34 @@ Troubleshooting:
+ packet is for new flow but maxflows is already reached,
+ all flows in export packets that got socket error.
+
+-> sock0: 10.0.0.2:2055, sndbuf 106496, filled 0, peak 106848; err: sndbuf reached 928, other 0
++> Natevents disabled, count start 0, stop 0.
++
++ - Natevents mode disabled or enabled, and how much start or stop events
++ are reported.
++
++> sock0: 10.0.0.2:2055 unconnected (1 attempts).
++
++ If socket is unconnected (for example if module loaded before interfaces is
++ up) it shows now much connection attempts was failed. It will try to connect
++ until success.
++
++> sock0: 10.0.0.2:2055, sndbuf 106496, filled 0, peak 106848; err: sndbuf reached 928, connect 0, other 0
+
+ sockX: per destination stats for:
+ X.X.X.X:Y: destination ip address and port.
+ - controlled by sysctl net.netflow.destination variable.
+ sndbuf X: how much data socket can hold in buffers.
+ - controlled by sysctl net.netflow.sndbuf variable.
+- - if you have packet drops due to sndbuf reached (error -11) increase this value.
++ - if you have packet drops due to sndbuf reached (error -11) increase this
++ value.
+ filled X: how much data in socket buffers right now.
+ peak X: peak value of how much data in socket buffers was.
+ - you will be interested to keep it below sndbuf value.
+ err: how much packets are dropped due to errors.
+ - all flows from them will be accounted in drop stat.
+- sndbuf reached X: how much packets dropped due to sndbuf being too small (error -11).
++ sndbuf reached X: how much packets dropped due to sndbuf being too small
++ (error -11).
++ connect X: how much connection attempts was failed.
+ other X: dropped due to other possible errors.
+
+ > aggr0: ...
+diff --git a/README.promisc b/README.promisc
+index 60ca922..31d774f 100644
+--- a/README.promisc
++++ b/README.promisc
+@@ -2,9 +2,14 @@ Hello,
+
+ If you wish to account with netflow module traffic mirrored on switch you may follow this example:
+
+-**************
+-* Solution 1 *
+-**************
++
++ Solution 1: General kernel patch.
++ Solution 2: Alternative w/o kernel patch.
++
++
++ **************
++ * Solution 1 *
++ **************
+
+ 1. Patch your kernel with `raw_promisc.patch' to enable raw table to see promisc traffic.
+
+@@ -33,17 +38,7 @@ If you wish to account with netflow module traffic mirrored on switch you may fo
+ # /sbin/vconfig add eth1 47
+ # /sbin/ifconfig eth1.47 up
+
+-5. Recompile ipt_netflow module with #define RAW_PROMISC_HACK uncommented:
+-
+- Find this line in ipt_NETFLOW.c (should be line 7):
+-
+-//#define RAW_PROMISC_HACK
+-
+- And remove two slashes at beginning of the line, so it become like this:
+-
+-#define RAW_PROMISC_HACK
+-
+- Re-compile module:
++5. Compile module:
+
+ # make clean all install
+
+@@ -55,13 +50,14 @@ If you wish to account with netflow module traffic mirrored on switch you may fo
+
+ # /sbin/iptables -A PREROUTING -t raw -i eth1.47 -j NETFLOW
+
+-
+ Voila.
+
++ps. For Debian Squeeze instructions look at raw_promisc_debian_squeeze6.patch
+
+-**************
+-* Solution 2 *
+-**************
++
++ **************
++ * Solution 2 *
++ **************
+
+ By Anonymous.
+
+@@ -81,4 +77,3 @@ Sometimes you may need to run:
+
+ for this scheme to work.
+
+-
+diff --git a/configure b/configure
+index 677dd7f..3f10e2a 100755
+--- a/configure
++++ b/configure
+@@ -3,7 +3,7 @@
+ PATH=$PATH:/bin:/usr/bin:/usr/sbin:/sbin:/usr/local/sbin
+
+ error() {
+- echo "! Error: $@"
++ echo -e "! Error: $@"
+ exit 1
+ }
+
+@@ -56,19 +56,20 @@ get_lib_from_lib() {
+ }
+
+ iptables_inc() {
+- echo -n "Iptables include path: "
++ echo -n "Iptables include flags: "
+ if [ "$IPTINC" ]; then
+- echo "$IPTINC (user specified)"
+ IPTINC="-I$IPTINC"
++ echo "$IPTINC (user specified)"
++ elif [ "$PKGVER" ]; then
++ IPTINC="$PKGINC"
++ echo "$IPTINC (pkg-config)"
++ elif [ "$NOIPTSRC" ]; then
++ IPTINC=
++ echo "none (default)"
+ else
+- if [ "$PKGINC" ]; then
+- IPTINC="$PKGINC"
+- echo "$IPTINC (pkg-config)"
+- else
+- IPTINC="$IPTSRC/include"
+- echo "$IPTINC (from source)"
+- IPTINC="-I$IPTINC"
+- fi
++ IPTINC="$IPTSRC/include"
++ IPTINC="-I$IPTINC"
++ echo "$IPTINC (from source)"
+ fi
+ }
+
+@@ -109,7 +110,16 @@ try_dir2() {
+ test -d "$1" && try_dir `dirname $1` && return 0
+ }
+
+-iptables_ver() {
++check_pkg_config() {
++ test "$PKGWARN" && return 1
++ if ! which pkg-config >/dev/null 2>&1; then
++ echo "! You don't have pkg-config, it may be useful to install it."
++ PKGWARN=1
++ return 1
++ fi
++ return 0
++}
++iptables_find_version() {
+ echo -n "Iptables binary version: "
+ if [ "$IPTVER" ]; then
+ echo "$IPTVER (user specified)"
+@@ -121,6 +131,7 @@ iptables_ver() {
+ else
+ echo "no iptables binary found"
+ fi
++ check_pkg_config
+ PKGVER=`pkg-config --modversion xtables 2>/dev/null`
+ if [ "$PKGVER" ]; then
+ IPTVER="$PKGVER"
+@@ -131,44 +142,90 @@ iptables_ver() {
+ fi
+ }
+
+-iptables_dir() {
+- test "$IPTINC" && return 1
+- test "$PKGINC" && return 1
+-
+- VER="iptables-$IPTVER"
+- if [ "$IPTSRC" ]; then
+- echo "User specified source directory: $IPTSRC"
+- try_dir $IPTSRC || error "Specified directory is not iptables source.."
++compile_libitp_test() {
++ echo -n "Checking for presence of $@... "
++ echo "
++#define __EXPORTED_HEADERS__
++#include <$*>" > test.c
++ gcc -c test.c >/dev/null 2>&1
++ RET=$?
++ if [ $RET = 0 ]; then
++ echo Yes;
+ else
+- echo "Searching for $VER sources.."
+- try_dir "./$VER" && return 0
+- try_dir "../$VER" && return 0
+- try_dir "/usr/src/$VER" && return 0
+- try_dirg "iptables" && return 0
+- try_dirg "../iptables" && return 0
+- try_dirg "/usr/src/iptables" && return 0
+- try_dir2 `locate $VER/extensions | head -1` && return 0
+- error "Can not find iptables source directory, try setting it with --ipt-src="
++ echo No;
+ fi
++ rm -f test.c test.o
++ return $RET
+ }
+
+-iptables_pkg_config() {
++iptables_try_pkgconfig() {
+ if [ ! "$PKGVER" ]; then
++ check_pkg_config
++ PKGVER=`pkg-config --modversion xtables 2>/dev/null`
++ TRYPKGVER=`pkg-config --modversion xtables 2>/dev/null`
+ echo -n "pkg-config for version $IPTVER exists: "
+- PKGVER=`pkg-config --exact-version=$IPTVER --modversion xtables 2>/dev/null`
++ pkg-config --exact-version=$IPTVER xtables 2>/dev/null
+ if [ $? = 0 ]; then
+ echo "Yes"
++ PKGVER=$TRYPKGVER
+ else
+- echo "No (reported: $PKGVER)"
+- unset PKGVER
++ if [ "$TRYPKGVER" ]; then
++ echo "No (reported: $TRYPKGVER)"
++ else
++ echo "No"
++ fi
+ fi
+ fi
+ if [ "$PKGVER" ]; then
++ check_pkg_config
++ PKGVER=`pkg-config --modversion xtables 2>/dev/null`
+ PKGINC=`pkg-config --cflags xtables`
+ PKGLIB=`pkg-config --variable=xtlibdir xtables`
+- IPTCFLAGS="-DXTABLES"
+ else
+- IPTCFLAGS="-DIPTABLES_VERSION=\\\\\"$IPTVER\\\\\""
++ # Newer versions of iptables should not have -I/kernel/include!
++ # So I assume that newer version will have correct pkg-config set up
++ # and if not, then it's older who need it.
++ IPTCFLAGS="-I$KDIR/include -DIPTABLES_VERSION=\\\\\"$IPTVER\\\\\""
++ fi
++ if compile_libitp_test xtables.h; then
++ IPTCFLAGS="-DXTABLES $IPTCFLAGS"
++ elif ! compile_libitp_test iptables.h; then
++ echo "! Iptables headers not found. You may need to specify --ipt-inc=..."
++ if [ -s /etc/debian_version ]; then
++ echo "! "
++ echo "! Under Debian simply run this:"
++ echo "! root# apt-get install iptables-dev pkg-config"
++ elif [ -s /etc/redhat-release ]; then
++ echo "! "
++ arch=.`uname -m`
++ echo "! Under Centos simply run this:"
++ echo "! root# yum install iptables-devel$arch pkgconfig"
++ fi
++ exit 1
++ fi
++
++}
++
++iptables_find_src() {
++ test "$IPTINC" && return 1
++ test "$PKGVER" && return 1
++
++ VER="iptables-$IPTVER"
++ if [ "$IPTSRC" ]; then
++ echo "User specified source directory: $IPTSRC"
++ try_dir $IPTSRC || error "Specified directory is not iptables source.."
++ else
++ echo "Searching for $VER sources.."
++ try_dir "./$VER" && return 0
++ try_dir "../$VER" && return 0
++ try_dir "/usr/src/$VER" && return 0
++ try_dirg "iptables" && return 0
++ try_dirg "../iptables" && return 0
++ try_dirg "/usr/src/iptables" && return 0
++ try_dir2 `locate $VER/extensions 2>/dev/null | head -1` && return 0
++ echo "! Can not find iptables source directory, you may try setting it with --ipt-src="
++ echo "! This is not fatal error, yet. Will be just using default include dir."
++ NOIPTSRC=1
+ fi
+ }
+
+@@ -206,18 +263,110 @@ do
+ esac
+ done
+
+-test "$KVERSION" || KVERSION=`uname -r`
+-echo Kernel version: $KVERSION
++kernel_find_version() {
++ KHOW=requested
++ test "$KVERSION" && return 0
++
++ if grep -q '#.*Debian' /proc/version; then
++ KHOW=proc
++ KVERSION=`sed -n 's/.*#.*Debian \([0-9\.]\+\)-.*/\1/p' /proc/version`
++ KLIBMOD=`uname -r`
++ else
++ KHOW=uname
++ KVERSION=`uname -r`
++ fi
++ test "$KDIR" || return 0
++
++ test -s $KDIR/Makefile || return 1
++ test -s $KDIR/include/config/kernel.release || return 1
++ KVERSION=`cat $KDIR/include/config/kernel.release`
++ KHOW=sources
++}
++
++kernel_check_src() {
++ if [ -s "$1/Makefile" ]; then
++ KDIR="$1"
++ return 0
++ fi
++ return 1
++}
++
++kernel_find_source() {
++ KSHOW=requested
++ test "$KDIR" && return 0
++ KSHOW=found
++ kernel_check_src /lib/modules/$KLIBMOD/build && return 0
++ kernel_check_src /lib/modules/$KVERSION/build && return 0
++ kernel_check_src /usr/src/kernels/$KVERSION && return 0
++ kernel_check_src /usr/src/linux-$KVERSION && return 0
++ echo "! Linux source not found. Don't panic. You may specify kernel source"
++ echo "! directory with --kdir=..., or try to install kernel-devel package,"
++ echo "! or just raw sources for linux-$KVERSION from kernel.org."
++ if grep -q -i centos /proc/version 2>/dev/null; then
++ echo "! "
++ arch=.`uname -m`
++ echo "! Under Centos simply run this:"
++ echo "! root# yum install kernel-devel iptables-devel$arch pkgconfig"
++ fi
++ if grep -q -i debian /proc/version 2>/dev/null; then
++ echo "! "
++ echo "! Under Debian simply run this:"
++ echo "! root# apt-get install module-assistant iptables-dev pkg-config"
++ echo "! root# m-a prepare"
++ fi
++ exit 1
++}
++
++kernel_check_consistency() {
++ if test -s $KDIR/include/config/kernel.release; then
++ SRCVER=`cat $KDIR/include/config/kernel.release`
++ test "$KVERSION" != "$SRCVER" && error "$KHOW kernel version ($KVERSION) and $KSHOW version of kernel source ($SRCVER) doesn't match!\n!" \
++ "You may try to specify only kernel source tree with --kdir=$KDIR\n!" \
++ "and configure will pick up version properly."
++ else
++ test -e "$KDIR/.config" || error ".config in kernel source not found, run make menuconfig in $KDIR"
++ test -d "$KDIR/include/config" || error "kernel is not prepared, run make prepare modules_prepare in $KDIR"
++ fi
++}
++
++kconfig() {
++ KCONFIG=$KDIR/.config
++ if ! grep -q "^$1=" $KCONFIG 2>/dev/null; then
++ if [ "$KCONFIGREPORTED" != true ]; then
++ KCONFIGREPORTED=true
++ echo Kernel config file checked: $KCONFIG
++ echo
++ fi
++ echo "! Attention: $1 is undefined in your kernel configuration"
++ echo "! Without this option enabled $2 will not work."
++ echo
++ fi
++}
++
++kernel_check_config() {
++ kconfig CONFIG_SYSCTL "sysctl interface"
++ kconfig CONFIG_PROC_FS "proc interface"
++ kconfig CONFIG_NF_NAT_NEEDED "natevents"
++ kconfig CONFIG_NF_CONNTRACK_EVENTS "natevents"
++ kconfig CONFIG_NF_CONNTRACK_MARK "connmark tracking"
++ kconfig CONFIG_IPV6 "IPv6"
++ kconfig CONFIG_IP6_NF_IPTABLES "ip6tables target"
++}
+
+-test "$KDIR" || KDIR=/lib/modules/$KVERSION/build
+-echo Kernel sources: $KDIR
++kernel_find_version #KVERSION
++test "$KLIBMOD" || KLIBMOD=$KVERSION
++echo "Kernel version: $KVERSION ($KHOW)"
++kernel_find_source #KDIR
++echo "Kernel sources: $KDIR ($KSHOW)"
++kernel_check_consistency
++kernel_check_config
+
+ test "$IPTBIN" || IPTBIN=`which iptables`
+
+-iptables_ver #IPTVER
+-iptables_pkg_config
+-iptables_dir #IPTSRC
+-iptables_src_version #check IPTSRC match to IPTVER
++iptables_find_version #IPTVER
++iptables_try_pkgconfig #try to configure from pkg-config
++iptables_find_src #IPTSRC
++iptables_src_version #check that IPTSRC match to IPTVER
+ iptables_inc #IPTINC
+ iptables_modules #IPTLIB
+
+@@ -225,7 +374,6 @@ REPLACE="\
+ s!@KVERSION@!$KVERSION!;\
+ s!@KDIR@!$KDIR!;\
+ s!@IPTABLES_VERSION@!$IPTVER!;\
+-s!@IPTABLES_INCLUDES@!$IPTINC!;\
+ s!@IPTABLES_CFLAGS@!$IPTCFLAGS $IPTINC!;\
+ s!@IPTABLES_MODULES@!$IPTLIB!"
+
+diff --git a/ipt_NETFLOW.c b/ipt_NETFLOW.c
+index d4c91e1..ad974c5 100644
+--- a/ipt_NETFLOW.c
++++ b/ipt_NETFLOW.c
+@@ -1,6 +1,6 @@
+ /*
+ * This is NetFlow exporting module (NETFLOW target) for linux
+- * (c) 2008-2012 <abc@telekom.ru>
++ * (c) 2008-2013 <abc@telekom.ru>
+ *
+ *
+ * This program is free software: you can redistribute it and/or modify
+@@ -18,8 +18,6 @@
+ *
+ */
+
+-//#define RAW_PROMISC_HACK
+-
+ #include <linux/module.h>
+ #include <linux/skbuff.h>
+ #include <linux/proc_fs.h>
+@@ -31,16 +29,26 @@
+ #include <linux/icmp.h>
+ #include <linux/igmp.h>
+ #include <linux/inetdevice.h>
+-#include <linux/jhash.h>
++#include <linux/hash.h>
++#include <linux/delay.h>
++#include <linux/spinlock_types.h>
+ #include <net/icmp.h>
+ #include <net/ip.h>
++#include <net/ipv6.h>
+ #include <net/tcp.h>
+ #include <net/route.h>
++#include <net/ip6_fib.h>
+ #include <net/dst.h>
+ #include <linux/netfilter_ipv4/ip_tables.h>
++#if defined(CONFIG_NF_NAT_NEEDED) || defined(CONFIG_NF_CONNTRACK_MARK)
++#include <linux/notifier.h>
++#include <net/netfilter/nf_conntrack.h>
++#include <net/netfilter/nf_conntrack_core.h>
++#endif
+ #include <linux/version.h>
+ #include <asm/unaligned.h>
+ #include "ipt_NETFLOW.h"
++#include "murmur3.h"
+ #ifdef CONFIG_BRIDGE_NETFILTER
+ #include <linux/netfilter_bridge.h>
+ #endif
+@@ -74,41 +82,66 @@
+ #define ipt_target xt_target
+ #endif
+
+-#define IPT_NETFLOW_VERSION "1.8"
++#define IPT_NETFLOW_VERSION "1.8.2" /* Note that if you are using git, you
++ will see version in other format. */
++#include "version.h"
++#ifdef GITVERSION
++#undef IPT_NETFLOW_VERSION
++#define IPT_NETFLOW_VERSION GITVERSION
++#endif
+
+ MODULE_LICENSE("GPL");
+ MODULE_AUTHOR("<abc@telekom.ru>");
+ MODULE_DESCRIPTION("iptables NETFLOW target module");
+ MODULE_VERSION(IPT_NETFLOW_VERSION);
++MODULE_ALIAS("ip6t_NETFLOW");
+
+ #define DST_SIZE 256
+ static char destination_buf[DST_SIZE] = "127.0.0.1:2055";
+ static char *destination = destination_buf;
+-module_param(destination, charp, 0400);
++module_param(destination, charp, 0444);
+ MODULE_PARM_DESC(destination, "export destination ipaddress:port");
+
+ static int inactive_timeout = 15;
+-module_param(inactive_timeout, int, 0600);
++module_param(inactive_timeout, int, 0644);
+ MODULE_PARM_DESC(inactive_timeout, "inactive flows timeout in seconds");
+
+ static int active_timeout = 30 * 60;
+-module_param(active_timeout, int, 0600);
++module_param(active_timeout, int, 0644);
+ MODULE_PARM_DESC(active_timeout, "active flows timeout in seconds");
+
+ static int debug = 0;
+-module_param(debug, int, 0600);
++module_param(debug, int, 0644);
+ MODULE_PARM_DESC(debug, "debug verbosity level");
+
+ static int sndbuf;
+-module_param(sndbuf, int, 0400);
++module_param(sndbuf, int, 0444);
+ MODULE_PARM_DESC(sndbuf, "udp socket SNDBUF size");
+
++static int protocol = 5;
++module_param(protocol, int, 0444);
++MODULE_PARM_DESC(protocol, "netflow protocol version (5, 9, 10)");
++
++static unsigned int refresh_rate = 20;
++module_param(refresh_rate, uint, 0644);
++MODULE_PARM_DESC(refresh_rate, "NetFlow v9/IPFIX refresh rate (packets)");
++
++static unsigned int timeout_rate = 30;
++module_param(timeout_rate, uint, 0644);
++MODULE_PARM_DESC(timeout_rate, "NetFlow v9/IPFIX timeout rate (minutes)");
++
++#ifdef CONFIG_NF_NAT_NEEDED
++static int natevents = 0;
++module_param(natevents, int, 0444);
++MODULE_PARM_DESC(natevents, "send NAT Events");
++#endif
++
+ static int hashsize;
+-module_param(hashsize, int, 0400);
++module_param(hashsize, int, 0444);
+ MODULE_PARM_DESC(hashsize, "hash table size");
+
+ static int maxflows = 2000000;
+-module_param(maxflows, int, 0600);
++module_param(maxflows, int, 0644);
+ MODULE_PARM_DESC(maxflows, "maximum number of flows");
+ static int peakflows = 0;
+ static unsigned long peakflows_at;
+@@ -121,22 +154,52 @@ MODULE_PARM_DESC(aggregation, "aggregation ruleset");
+
+ static DEFINE_PER_CPU(struct ipt_netflow_stat, ipt_netflow_stat);
+ static LIST_HEAD(usock_list);
+-static DEFINE_RWLOCK(sock_lock);
++static DEFINE_MUTEX(sock_lock);
+
++#define LOCK_COUNT (1<<8)
++#define LOCK_COUNT_MASK (LOCK_COUNT-1)
++static spinlock_t htable_locks[LOCK_COUNT] = {
++ [0 ... LOCK_COUNT - 1] = __SPIN_LOCK_UNLOCKED(htable_locks)
++};
++static DEFINE_RWLOCK(htable_rwlock); /* global lock to protect htable_locks change */
+ static unsigned int ipt_netflow_hash_rnd;
+-struct hlist_head *ipt_netflow_hash __read_mostly; /* hash table memory */
++static struct hlist_head *ipt_netflow_hash __read_mostly; /* hash table memory */
+ static unsigned int ipt_netflow_hash_size __read_mostly = 0; /* buckets */
+ static LIST_HEAD(ipt_netflow_list); /* all flows */
++static DEFINE_SPINLOCK(hlist_lock); /* should almost always be locked w/o _bh */
+ static LIST_HEAD(aggr_n_list);
+ static LIST_HEAD(aggr_p_list);
+ static DEFINE_RWLOCK(aggr_lock);
++#ifdef CONFIG_NF_NAT_NEEDED
++static LIST_HEAD(nat_list); /* nat events */
++static DEFINE_SPINLOCK(nat_lock);
++static unsigned long nat_events_start = 0;
++static unsigned long nat_events_stop = 0;
++#endif
+ static struct kmem_cache *ipt_netflow_cachep __read_mostly; /* ipt_netflow memory */
+ static atomic_t ipt_netflow_count = ATOMIC_INIT(0);
+-static DEFINE_SPINLOCK(ipt_netflow_lock); /* hash table lock */
+
+-static long long pdu_packets = 0, pdu_traf = 0;
+-static struct netflow5_pdu pdu;
+-static unsigned long pdu_ts_mod;
++static long long pdu_packets = 0, pdu_traf = 0; /* how much accounted traffic in pdu */
++static unsigned int pdu_count = 0;
++static unsigned int pdu_seq = 0;
++static unsigned int pdu_data_records = 0;
++static unsigned int pdu_tpl_records = 0;
++static unsigned long pdu_ts_mod; /* ts of last flow */
++static union {
++ struct netflow5_pdu v5;
++ struct netflow9_pdu v9;
++ struct ipfix_pdu ipfix;
++} pdu;
++static int engine_id = 0; /* Observation Domain */
++static __u8 *pdu_data_used;
++static __u8 *pdu_high_wm; /* high watermark */
++static unsigned int pdu_max_size; /* sizeof pdu */
++static struct flowset_data *pdu_flowset = NULL; /* current data flowset */
++
++static void (*netflow_export_flow)(struct ipt_netflow *nf);
++static void (*netflow_export_pdu)(void); /* called on timeout */
++static void netflow_switch_version(int ver);
++
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+ static void netflow_work_fn(void *work);
+ static DECLARE_WORK(netflow_work, netflow_work_fn, NULL);
+@@ -146,19 +209,26 @@ static DECLARE_DELAYED_WORK(netflow_work, netflow_work_fn);
+ #endif
+ static struct timer_list rate_timer;
+
++#define TCP_SYN_ACK 0x12
+ #define TCP_FIN_RST 0x05
+
+ static long long sec_prate = 0, sec_brate = 0;
+ static long long min_prate = 0, min_brate = 0;
+ static long long min5_prate = 0, min5_brate = 0;
+-static unsigned int metric = 10, min15_metric = 10, min5_metric = 10, min_metric = 10; /* hash metrics */
++static unsigned int metric = 100, min15_metric = 100, min5_metric = 100, min_metric = 100; /* hash metrics */
+
+ static int set_hashsize(int new_size);
+ static void destination_removeall(void);
+ static int add_destinations(char *ptr);
+ static void aggregation_remove(struct list_head *list);
+ static int add_aggregation(char *ptr);
+-static void netflow_scan_and_export(int flush);
++static int netflow_scan_and_export(int flush);
++enum {
++ DONT_FLUSH, AND_FLUSH
++};
++static int template_ids = FLOWSET_DATA_FIRST;
++static int tpl_count = 0; /* how much active templates */
++
+
+ static inline __be32 bits2mask(int bits) {
+ return (bits? 0xffffffff << (32 - bits) : 0);
+@@ -175,28 +245,46 @@ static inline int mask2bits(__be32 mask) {
+ /* under that lock worker is always stopped and not rescheduled,
+ * and we can call worker sub-functions manually */
+ static DEFINE_MUTEX(worker_lock);
+-static inline void __start_scan_worker(void)
++#define MIN_DELAY 1
++#define MAX_DELAY (HZ / 10)
++static int worker_delay = HZ / 10;
++static inline void _schedule_scan_worker(const int status)
+ {
+- schedule_delayed_work(&netflow_work, HZ / 10);
++ /* rudimentary congestion avoidance */
++ if (status > 0)
++ worker_delay -= status;
++ else if (status < 0)
++ worker_delay /= 2;
++ else
++ worker_delay++;
++ if (worker_delay < MIN_DELAY)
++ worker_delay = MIN_DELAY;
++ else if (worker_delay > MAX_DELAY)
++ worker_delay = MAX_DELAY;
++ schedule_delayed_work(&netflow_work, worker_delay);
+ }
+
+-static inline void start_scan_worker(void)
++/* This is only called soon after pause_scan_worker. */
++static inline void cont_scan_worker(void)
+ {
+- __start_scan_worker();
++ _schedule_scan_worker(0);
+ mutex_unlock(&worker_lock);
+ }
+
+-/* we always stop scanner before write_lock(&sock_lock)
+- * to let it never hold that spin lock */
+-static inline void __stop_scan_worker(void)
++static inline void _unschedule_scan_worker(void)
+ {
++#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23)
++ cancel_rearming_delayed_work(&netflow_work);
++#else
+ cancel_delayed_work_sync(&netflow_work);
++#endif
+ }
+
+-static inline void stop_scan_worker(void)
++/* This is only used for quick pause (in procctl). */
++static inline void pause_scan_worker(void)
+ {
+ mutex_lock(&worker_lock);
+- __stop_scan_worker();
++ _unschedule_scan_worker();
+ }
+
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+@@ -223,11 +311,14 @@ static int nf_seq_show(struct seq_file *seq, void *v)
+ int snum = 0;
+ int peak = (jiffies - peakflows_at) / HZ;
+
+- seq_printf(seq, "Flows: active %u (peak %u reached %ud%uh%um ago), mem %uK\n",
++ seq_printf(seq, "ipt_NETFLOW version " IPT_NETFLOW_VERSION ", srcversion %s\n",
++ THIS_MODULE->srcversion);
++ seq_printf(seq, "Flows: active %u (peak %u reached %ud%uh%um ago), mem %uK, worker delay %d/%d.\n",
+ nr_flows,
+ peakflows,
+ peak / (60 * 60 * 24), (peak / (60 * 60)) % 24, (peak / 60) % 60,
+- (unsigned int)((nr_flows * sizeof(struct ipt_netflow)) >> 10));
++ (unsigned int)((nr_flows * sizeof(struct ipt_netflow)) >> 10),
++ worker_delay, HZ);
+
+ for_each_present_cpu(cpu) {
+ struct ipt_netflow_stat *st = &per_cpu(ipt_netflow_stat, cpu);
+@@ -252,93 +343,123 @@ static int nf_seq_show(struct seq_file *seq, void *v)
+ }
+
+ #define FFLOAT(x, prec) (int)(x) / prec, (int)(x) % prec
+- seq_printf(seq, "Hash: size %u (mem %uK), metric %d.%d, %d.%d, %d.%d, %d.%d. MemTraf: %llu pkt, %llu K (pdu %llu, %llu).\n",
+- ipt_netflow_hash_size,
+- (unsigned int)((ipt_netflow_hash_size * sizeof(struct hlist_head)) >> 10),
+- FFLOAT(metric, 10),
+- FFLOAT(min_metric, 10),
+- FFLOAT(min5_metric, 10),
+- FFLOAT(min15_metric, 10),
+- pkt_total - pkt_out + pdu_packets,
+- (traf_total - traf_out + pdu_traf) >> 10,
+- pdu_packets,
+- pdu_traf);
+-
+- seq_printf(seq, "Timeout: active %d, inactive %d. Maxflows %u\n",
+- active_timeout,
+- inactive_timeout,
+- maxflows);
+-
+- seq_printf(seq, "Rate: %llu bits/sec, %llu packets/sec; Avg 1 min: %llu bps, %llu pps; 5 min: %llu bps, %llu pps\n",
+- sec_brate, sec_prate, min_brate, min_prate, min5_brate, min5_prate);
+-
+- seq_printf(seq, "cpu# stat: <search found new, trunc frag alloc maxflows>, sock: <ok fail cberr, bytes>, traffic: <pkt, bytes>, drop: <pkt, bytes>\n");
+-
+- seq_printf(seq, "Total stat: %6llu %6llu %6llu, %4u %4u %4u %4u, sock: %6u %u %u, %llu K, traffic: %llu, %llu MB, drop: %llu, %llu K\n",
+- (unsigned long long)searched,
+- (unsigned long long)found,
+- (unsigned long long)notfound,
+- truncated, frags, alloc_err, maxflows_err,
+- send_success, send_failed, sock_errors,
+- (unsigned long long)exported_size >> 10,
+- (unsigned long long)pkt_total, (unsigned long long)traf_total >> 20,
+- (unsigned long long)pkt_drop, (unsigned long long)traf_drop >> 10);
++ seq_printf(seq, "Hash: size %u (mem %uK), metric %d.%02d [%d.%02d, %d.%02d, %d.%02d]."
++ " MemTraf: %llu pkt, %llu K (pdu %llu, %llu), Out %llu pkt, %llu K.\n",
++ ipt_netflow_hash_size,
++ (unsigned int)((ipt_netflow_hash_size * sizeof(struct hlist_head)) >> 10),
++ FFLOAT(metric, 100),
++ FFLOAT(min_metric, 100),
++ FFLOAT(min5_metric, 100),
++ FFLOAT(min15_metric, 100),
++ pkt_total - pkt_out + pdu_packets,
++ (traf_total - traf_out + pdu_traf) >> 10,
++ pdu_packets,
++ pdu_traf,
++ pkt_out,
++ traf_out >> 10);
++
++ seq_printf(seq, "Rate: %llu bits/sec, %llu packets/sec;"
++ " Avg 1 min: %llu bps, %llu pps; 5 min: %llu bps, %llu pps\n",
++ sec_brate, sec_prate, min_brate, min_prate, min5_brate, min5_prate);
++
++ seq_printf(seq, "cpu# stat: <search found new [metric], trunc frag alloc maxflows>,"
++ " sock: <ok fail cberr, bytes>, traffic: <pkt, bytes>, drop: <pkt, bytes>\n");
++
++#define SAFEDIV(x,y) ((y)? ({ u64 __tmp = x; do_div(__tmp, y); (int)__tmp; }) : 0)
++ seq_printf(seq, "Total stat: %6llu %6llu %6llu [%d.%02d], %4u %4u %4u %4u,"
++ " sock: %6u %u %u, %llu K, traffic: %llu, %llu MB, drop: %llu, %llu K\n",
++ searched,
++ (unsigned long long)found,
++ (unsigned long long)notfound,
++ FFLOAT(SAFEDIV(100LL * (searched + found + notfound), (found + notfound)), 100),
++ truncated, frags, alloc_err, maxflows_err,
++ send_success, send_failed, sock_errors,
++ (unsigned long long)exported_size >> 10,
++ (unsigned long long)pkt_total, (unsigned long long)traf_total >> 20,
++ (unsigned long long)pkt_drop, (unsigned long long)traf_drop >> 10);
+
+ if (num_present_cpus() > 1) {
+ for_each_present_cpu(cpu) {
+ struct ipt_netflow_stat *st;
+
+ st = &per_cpu(ipt_netflow_stat, cpu);
+- seq_printf(seq, "cpu%u stat: %6llu %6llu %6llu, %4u %4u %4u %4u, sock: %6u %u %u, %llu K, traffic: %llu, %llu MB, drop: %llu, %llu K\n",
+- cpu,
+- (unsigned long long)st->searched,
+- (unsigned long long)st->found,
+- (unsigned long long)st->notfound,
+- st->truncated, st->frags, st->alloc_err, st->maxflows_err,
+- st->send_success, st->send_failed, st->sock_errors,
+- (unsigned long long)st->exported_size >> 10,
+- (unsigned long long)st->pkt_total, (unsigned long long)st->traf_total >> 20,
+- (unsigned long long)st->pkt_drop, (unsigned long long)st->traf_drop >> 10);
++ seq_printf(seq, "cpu%u stat: %6llu %6llu %6llu [%d.%02d], %4u %4u %4u %4u,"
++ " sock: %6u %u %u, %llu K, traffic: %llu, %llu MB, drop: %llu, %llu K\n",
++ cpu,
++ (unsigned long long)st->searched,
++ (unsigned long long)st->found,
++ (unsigned long long)st->notfound,
++ FFLOAT(SAFEDIV(100LL * (st->searched + st->found + st->notfound), (st->found + st->notfound)), 100),
++ st->truncated, st->frags, st->alloc_err, st->maxflows_err,
++ st->send_success, st->send_failed, st->sock_errors,
++ (unsigned long long)st->exported_size >> 10,
++ (unsigned long long)st->pkt_total, (unsigned long long)st->traf_total >> 20,
++ (unsigned long long)st->pkt_drop, (unsigned long long)st->traf_drop >> 10);
+ }
+ }
+
+- read_lock(&sock_lock);
++ seq_printf(seq, "Protocol version %d", protocol);
++ if (protocol == 10)
++ seq_printf(seq, " (ipfix)");
++ else
++ seq_printf(seq, " (netflow)");
++ if (protocol >= 9)
++ seq_printf(seq, ", refresh-rate %u, timeout-rate %u, (templates %d, active %d)",
++ refresh_rate, timeout_rate, template_ids - FLOWSET_DATA_FIRST, tpl_count);
++
++ seq_printf(seq, ". Timeouts: active %d, inactive %d. Maxflows %u\n",
++ active_timeout,
++ inactive_timeout,
++ maxflows);
++
++#ifdef CONFIG_NF_NAT_NEEDED
++ seq_printf(seq, "Natevents %s, count start %lu, stop %lu.\n", natevents? "enabled" : "disabled",
++ nat_events_start, nat_events_stop);
++#endif
++
++ mutex_lock(&sock_lock);
+ list_for_each_entry(usock, &usock_list, list) {
+- struct sock *sk = usock->sock->sk;
+-
+- seq_printf(seq, "sock%d: %u.%u.%u.%u:%u, sndbuf %u, filled %u, peak %u; err: sndbuf reached %u, other %u\n",
+- snum,
+- usock->ipaddr >> 24,
+- (usock->ipaddr >> 16) & 255,
+- (usock->ipaddr >> 8) & 255,
+- usock->ipaddr & 255,
+- usock->port,
+- sk->sk_sndbuf,
+- atomic_read(&sk->sk_wmem_alloc),
+- atomic_read(&usock->wmem_peak),
+- atomic_read(&usock->err_full),
+- atomic_read(&usock->err_other));
++ seq_printf(seq, "sock%d: %u.%u.%u.%u:%u",
++ snum,
++ HIPQUAD(usock->ipaddr),
++ usock->port);
++ if (usock->sock) {
++ struct sock *sk = usock->sock->sk;
++
++ seq_printf(seq, ", sndbuf %u, filled %u, peak %u;"
++ " err: sndbuf reached %u, connect %u, other %u\n",
++ sk->sk_sndbuf,
++ atomic_read(&sk->sk_wmem_alloc),
++ atomic_read(&usock->wmem_peak),
++ atomic_read(&usock->err_full),
++ atomic_read(&usock->err_connect),
++ atomic_read(&usock->err_other));
++ } else
++ seq_printf(seq, " unconnected (%u attempts).\n",
++ atomic_read(&usock->err_connect));
+ snum++;
+ }
+- read_unlock(&sock_lock);
++ mutex_unlock(&sock_lock);
+
+ read_lock_bh(&aggr_lock);
+ snum = 0;
+ list_for_each_entry(aggr_n, &aggr_n_list, list) {
+- seq_printf(seq, "aggr#%d net: match %u.%u.%u.%u/%d strip %d\n",
+- snum,
+- HIPQUAD(aggr_n->addr),
+- mask2bits(aggr_n->mask),
+- mask2bits(aggr_n->aggr_mask));
++ seq_printf(seq, "aggr#%d net: match %u.%u.%u.%u/%d strip %d (usage %u)\n",
++ snum,
++ HIPQUAD(aggr_n->addr),
++ mask2bits(aggr_n->mask),
++ mask2bits(aggr_n->aggr_mask),
++ atomic_read(&aggr_n->usage));
+ snum++;
+ }
+ snum = 0;
+ list_for_each_entry(aggr_p, &aggr_p_list, list) {
+- seq_printf(seq, "aggr#%d port: ports %u-%u replace %u\n",
+- snum,
+- aggr_p->port1,
+- aggr_p->port2,
+- aggr_p->aggr_port);
++ seq_printf(seq, "aggr#%d port: ports %u-%u replace %u (usage %u)\n",
++ snum,
++ aggr_p->port1,
++ aggr_p->port2,
++ aggr_p->aggr_port,
++ atomic_read(&aggr_p->usage));
+ snum++;
+ }
+ read_unlock_bh(&aggr_lock);
+@@ -367,8 +488,13 @@ static struct file_operations nf_seq_fops = {
+ #define BEFORE2632(x,y)
+ #endif
+
++/* PAX need to know that we are allowed to write */
++#ifndef CONSTIFY_PLUGIN
++#define ctl_table_no_const ctl_table
++#endif
++
+ /* sysctl /proc/sys/net/netflow */
+-static int hsize_procctl(ctl_table *ctl, int write, BEFORE2632(struct file *filp,)
++static int hsize_procctl(ctl_table_no_const *ctl, int write, BEFORE2632(struct file *filp,)
+ void __user *buffer, size_t *lenp, loff_t *fpos)
+ {
+ void *orig = ctl->data;
+@@ -386,20 +512,21 @@ static int hsize_procctl(ctl_table *ctl, int write, BEFORE2632(struct file *filp
+ return ret;
+ }
+
+-static int sndbuf_procctl(ctl_table *ctl, int write, BEFORE2632(struct file *filp,)
++static int sndbuf_procctl(ctl_table_no_const *ctl, int write, BEFORE2632(struct file *filp,)
+ void __user *buffer, size_t *lenp, loff_t *fpos)
+ {
+ int ret;
+ struct ipt_netflow_sock *usock;
+-
+- read_lock(&sock_lock);
++
++ mutex_lock(&sock_lock);
+ if (list_empty(&usock_list)) {
+- read_unlock(&sock_lock);
++ mutex_unlock(&sock_lock);
+ return -ENOENT;
+ }
+ usock = list_first_entry(&usock_list, struct ipt_netflow_sock, list);
+- sndbuf = usock->sock->sk->sk_sndbuf;
+- read_unlock(&sock_lock);
++ if (usock->sock)
++ sndbuf = usock->sock->sk->sk_sndbuf;
++ mutex_unlock(&sock_lock);
+
+ ctl->data = &sndbuf;
+ ret = proc_dointvec(ctl, write, BEFORE2632(filp,) buffer, lenp, fpos);
+@@ -407,13 +534,14 @@ static int sndbuf_procctl(ctl_table *ctl, int write, BEFORE2632(struct file *fil
+ return ret;
+ if (sndbuf < SOCK_MIN_SNDBUF)
+ sndbuf = SOCK_MIN_SNDBUF;
+- stop_scan_worker();
+- write_lock(&sock_lock);
++ pause_scan_worker();
++ mutex_lock(&sock_lock);
+ list_for_each_entry(usock, &usock_list, list) {
+- usock->sock->sk->sk_sndbuf = sndbuf;
++ if (usock->sock)
++ usock->sock->sk->sk_sndbuf = sndbuf;
+ }
+- write_unlock(&sock_lock);
+- start_scan_worker();
++ mutex_unlock(&sock_lock);
++ cont_scan_worker();
+ return ret;
+ }
+
+@@ -424,10 +552,10 @@ static int destination_procctl(ctl_table *ctl, int write, BEFORE2632(struct file
+
+ ret = proc_dostring(ctl, write, BEFORE2632(filp,) buffer, lenp, fpos);
+ if (ret >= 0 && write) {
+- stop_scan_worker();
++ pause_scan_worker();
+ destination_removeall();
+ add_destinations(destination_buf);
+- start_scan_worker();
++ cont_scan_worker();
+ }
+ return ret;
+ }
+@@ -446,13 +574,12 @@ static int aggregation_procctl(ctl_table *ctl, int write, BEFORE2632(struct file
+ return ret;
+ }
+
+-static int flush_procctl(ctl_table *ctl, int write, BEFORE2632(struct file *filp,)
++static int flush_procctl(ctl_table_no_const *ctl, int write, BEFORE2632(struct file *filp,)
+ void __user *buffer, size_t *lenp, loff_t *fpos)
+ {
+ int ret;
+- int val;
++ int val = 0;
+
+- val = 0;
+ ctl->data = &val;
+ ret = proc_dointvec(ctl, write, BEFORE2632(filp,) buffer, lenp, fpos);
+
+@@ -461,14 +588,67 @@ static int flush_procctl(ctl_table *ctl, int write, BEFORE2632(struct file *filp
+
+ if (val > 0) {
+ printk(KERN_INFO "ipt_NETFLOW: forced flush\n");
+- stop_scan_worker();
+- netflow_scan_and_export(1);
+- start_scan_worker();
++ pause_scan_worker();
++ netflow_scan_and_export(AND_FLUSH);
++ cont_scan_worker();
++ }
++
++ return ret;
++}
++
++static int protocol_procctl(ctl_table *ctl, int write, BEFORE2632(struct file *filp,)
++ void __user *buffer, size_t *lenp, loff_t *fpos)
++{
++ int ret;
++ int ver = protocol;
++
++ ctl->data = &ver;
++ ret = proc_dointvec(ctl, write, BEFORE2632(filp,) buffer, lenp, fpos);
++
++ if (!write)
++ return ret;
++
++ switch (ver) {
++ case 5:
++ case 9:
++ case 10:
++ printk(KERN_INFO "ipt_NETFLOW: forced flush (protocol version change)\n");
++ pause_scan_worker();
++ netflow_scan_and_export(AND_FLUSH);
++ netflow_switch_version(ver);
++ cont_scan_worker();
++ break;
++ default:
++ return -EPERM;
+ }
+
+ return ret;
+ }
+
++#ifdef CONFIG_NF_NAT_NEEDED
++static void register_ct_events(void);
++static void unregister_ct_events(void);
++static int natevents_procctl(ctl_table *ctl, int write, BEFORE2632(struct file *filp,)
++ void __user *buffer, size_t *lenp, loff_t *fpos)
++{
++ int ret;
++ int val = natevents;
++
++ ctl->data = &val;
++ ret = proc_dointvec(ctl, write, BEFORE2632(filp,) buffer, lenp, fpos);
++
++ if (!write)
++ return ret;
++
++ if (natevents && !val)
++ unregister_ct_events();
++ else if (!natevents && val)
++ register_ct_events();
++
++ return ret;
++}
++#endif
++
+ static struct ctl_table_header *netflow_sysctl_header;
+
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+@@ -547,6 +727,38 @@ static struct ctl_table netflow_sysctl_table[] = {
+ .maxlen = sizeof(int),
+ .proc_handler = &flush_procctl,
+ },
++ {
++ _CTL_NAME(10)
++ .procname = "protocol",
++ .mode = 0644,
++ .maxlen = sizeof(int),
++ .proc_handler = &protocol_procctl,
++ },
++ {
++ _CTL_NAME(11)
++ .procname = "refresh-rate",
++ .mode = 0644,
++ .data = &refresh_rate,
++ .maxlen = sizeof(int),
++ .proc_handler = &proc_dointvec,
++ },
++ {
++ _CTL_NAME(12)
++ .procname = "timeout-rate",
++ .mode = 0644,
++ .data = &timeout_rate,
++ .maxlen = sizeof(int),
++ .proc_handler = &proc_dointvec,
++ },
++#ifdef CONFIG_NF_NAT_NEEDED
++ {
++ _CTL_NAME(13)
++ .procname = "natevents",
++ .mode = 0644,
++ .maxlen = sizeof(int),
++ .proc_handler = &natevents_procctl,
++ },
++#endif
+ { }
+ };
+
+@@ -588,18 +800,69 @@ static struct ctl_path netflow_sysctl_path[] = {
+ static void sk_error_report(struct sock *sk)
+ {
+ /* clear connection refused errors if any */
+- write_lock_bh(&sk->sk_callback_lock);
+ if (debug > 1)
+- printk(KERN_INFO "NETFLOW: socket error <%d>\n", sk->sk_err);
++ printk(KERN_INFO "ipt_NETFLOW: socket error <%d>\n", sk->sk_err);
+ sk->sk_err = 0;
+ NETFLOW_STAT_INC(sock_errors);
+- write_unlock_bh(&sk->sk_callback_lock);
+ return;
+ }
+
++static struct socket *_usock_alloc(const __be32 ipaddr, const unsigned short port)
++{
++ struct sockaddr_in sin;
++ struct socket *sock;
++ int error;
++
++ if ((error = sock_create_kern(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &sock)) < 0) {
++ printk(KERN_ERR "ipt_NETFLOW: sock_create_kern error %d\n", -error);
++ return NULL;
++ }
++ sock->sk->sk_allocation = GFP_ATOMIC;
++ sock->sk->sk_prot->unhash(sock->sk); /* hidden from input */
++ sock->sk->sk_error_report = &sk_error_report; /* clear ECONNREFUSED */
++ if (sndbuf)
++ sock->sk->sk_sndbuf = sndbuf;
++ else
++ sndbuf = sock->sk->sk_sndbuf;
++ memset(&sin, 0, sizeof(sin));
++ sin.sin_family = AF_INET;
++ sin.sin_addr.s_addr = htonl(ipaddr);
++ sin.sin_port = htons(port);
++ if ((error = sock->ops->connect(sock, (struct sockaddr *)&sin,
++ sizeof(sin), 0)) < 0) {
++ printk(KERN_ERR "ipt_NETFLOW: error connecting UDP socket %d,"
++ " don't worry, will try reconnect later.\n", -error);
++ /* ENETUNREACH when no interfaces */
++ sock_release(sock);
++ return NULL;
++ }
++ return sock;
++}
++
++static void usock_connect(struct ipt_netflow_sock *usock, const int sendmsg)
++{
++ usock->sock = _usock_alloc(usock->ipaddr, usock->port);
++ if (usock->sock) {
++ if (sendmsg || debug)
++ printk(KERN_INFO "ipt_NETFLOW: connected %u.%u.%u.%u:%u\n",
++ HIPQUAD(usock->ipaddr),
++ usock->port);
++ } else {
++ atomic_inc(&usock->err_connect);
++ if (debug)
++ printk(KERN_INFO "ipt_NETFLOW: connect to %u.%u.%u.%u:%u failed%s.\n",
++ HIPQUAD(usock->ipaddr),
++ usock->port,
++ (sendmsg)? " (pdu lost)" : "");
++ }
++ atomic_set(&usock->wmem_peak, 0);
++ atomic_set(&usock->err_full, 0);
++ atomic_set(&usock->err_other, 0);
++}
++
+ // return numbers of sends succeded, 0 if none
+ /* only called in scan worker path */
+-static int netflow_send_pdu(void *buffer, int len)
++static void netflow_sendmsg(void *buffer, const int len)
+ {
+ struct msghdr msg = { .msg_flags = MSG_DONTWAIT|MSG_NOSIGNAL };
+ struct kvec iov = { buffer, len };
+@@ -607,9 +870,16 @@ static int netflow_send_pdu(void *buffer, int len)
+ int snum = 0;
+ struct ipt_netflow_sock *usock;
+
++ mutex_lock(&sock_lock);
+ list_for_each_entry(usock, &usock_list, list) {
++ if (!usock->sock)
++ usock_connect(usock, 1);
++ if (!usock->sock) {
++ NETFLOW_STAT_INC_ATOMIC(send_failed);
++ continue;
++ }
+ if (debug)
+- printk(KERN_INFO "netflow_send_pdu: sendmsg(%d, %d) [%u %u]\n",
++ printk(KERN_INFO "netflow_sendmsg: sendmsg(%d, %d) [%u %u]\n",
+ snum,
+ len,
+ atomic_read(&usock->sock->sk->sk_wmem_alloc),
+@@ -624,7 +894,7 @@ static int netflow_send_pdu(void *buffer, int len)
+ suggestion = ": increase sndbuf!";
+ } else
+ atomic_inc(&usock->err_other);
+- printk(KERN_ERR "netflow_send_pdu[%d]: sendmsg error %d: data loss %llu pkt, %llu bytes%s\n",
++ printk(KERN_ERR "ipt_NETFLOW: sendmsg[%d] error %d: data loss %llu pkt, %llu bytes%s\n",
+ snum, ret, pdu_packets, pdu_traf, suggestion);
+ } else {
+ unsigned int wmem = atomic_read(&usock->sock->sk->sk_wmem_alloc);
+@@ -636,98 +906,67 @@ static int netflow_send_pdu(void *buffer, int len)
+ }
+ snum++;
+ }
+- return retok;
++ mutex_unlock(&sock_lock);
++ if (retok == 0) {
++ /* not least one send succeded, account stat for dropped packets */
++ NETFLOW_STAT_ADD_ATOMIC(pkt_drop, pdu_packets);
++ NETFLOW_STAT_ADD_ATOMIC(traf_drop, pdu_traf);
++ }
+ }
+
+-static void usock_free(struct ipt_netflow_sock *usock)
++static void usock_close_free(struct ipt_netflow_sock *usock)
+ {
+- printk(KERN_INFO "netflow: remove destination %u.%u.%u.%u:%u (%p)\n",
++ printk(KERN_INFO "ipt_NETFLOW: removed destination %u.%u.%u.%u:%u\n",
+ HIPQUAD(usock->ipaddr),
+- usock->port,
+- usock->sock);
++ usock->port);
+ if (usock->sock)
+ sock_release(usock->sock);
+ usock->sock = NULL;
+- vfree(usock);
++ vfree(usock);
+ }
+
+ static void destination_removeall(void)
+ {
+- write_lock(&sock_lock);
++ mutex_lock(&sock_lock);
+ while (!list_empty(&usock_list)) {
+ struct ipt_netflow_sock *usock;
+
+ usock = list_entry(usock_list.next, struct ipt_netflow_sock, list);
+ list_del(&usock->list);
+- write_unlock(&sock_lock);
+- usock_free(usock);
+- write_lock(&sock_lock);
++ mutex_unlock(&sock_lock);
++ usock_close_free(usock);
++ mutex_lock(&sock_lock);
+ }
+- write_unlock(&sock_lock);
++ mutex_unlock(&sock_lock);
+ }
+
+ static void add_usock(struct ipt_netflow_sock *usock)
+ {
+ struct ipt_netflow_sock *sk;
+
+- /* don't need empty sockets */
+- if (!usock->sock) {
+- usock_free(usock);
+- return;
+- }
+-
+- write_lock(&sock_lock);
++ mutex_lock(&sock_lock);
+ /* don't need duplicated sockets */
+ list_for_each_entry(sk, &usock_list, list) {
+ if (sk->ipaddr == usock->ipaddr &&
+ sk->port == usock->port) {
+- write_unlock(&sock_lock);
+- usock_free(usock);
++ mutex_unlock(&sock_lock);
++ usock_close_free(usock);
+ return;
+ }
+ }
+ list_add_tail(&usock->list, &usock_list);
+- printk(KERN_INFO "netflow: added destination %u.%u.%u.%u:%u\n",
++ printk(KERN_INFO "ipt_NETFLOW: added destination %u.%u.%u.%u:%u%s\n",
+ HIPQUAD(usock->ipaddr),
+- usock->port);
+- write_unlock(&sock_lock);
+-}
+-
+-static struct socket *usock_alloc(__be32 ipaddr, unsigned short port)
+-{
+- struct sockaddr_in sin;
+- struct socket *sock;
+- int error;
+-
+- if ((error = sock_create_kern(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &sock)) < 0) {
+- printk(KERN_ERR "netflow: sock_create_kern error %d\n", error);
+- return NULL;
+- }
+- sock->sk->sk_allocation = GFP_ATOMIC;
+- sock->sk->sk_prot->unhash(sock->sk); /* hidden from input */
+- sock->sk->sk_error_report = &sk_error_report; /* clear ECONNREFUSED */
+- if (sndbuf)
+- sock->sk->sk_sndbuf = sndbuf;
+- else
+- sndbuf = sock->sk->sk_sndbuf;
+- memset(&sin, 0, sizeof(sin));
+- sin.sin_family = AF_INET;
+- sin.sin_addr.s_addr = htonl(ipaddr);
+- sin.sin_port = htons(port);
+- if ((error = sock->ops->connect(sock, (struct sockaddr *)&sin,
+- sizeof(sin), 0)) < 0) {
+- printk(KERN_ERR "netflow: error connecting UDP socket %d\n", error);
+- sock_release(sock);
+- return NULL;
+- }
+- return sock;
++ usock->port,
++ (!usock->sock)? " (unconnected)" : "");
++ mutex_unlock(&sock_lock);
+ }
+
+ #define SEPARATORS " ,;\t\n"
+ static int add_destinations(char *ptr)
+ {
+ while (ptr) {
+- unsigned char ip[4];
++ unsigned char ip[4];
+ unsigned short port;
+
+ ptr += strspn(ptr, SEPARATORS);
+@@ -737,17 +976,15 @@ static int add_destinations(char *ptr)
+ struct ipt_netflow_sock *usock;
+
+ if (!(usock = vmalloc(sizeof(*usock)))) {
+- printk(KERN_ERR "netflow: can't vmalloc socket\n");
++ printk(KERN_ERR "ipt_NETFLOW: can't vmalloc socket\n");
+ return -ENOMEM;
+ }
+
+ memset(usock, 0, sizeof(*usock));
++ atomic_set(&usock->err_connect, 0);
+ usock->ipaddr = ntohl(*(__be32 *)ip);
+ usock->port = port;
+- usock->sock = usock_alloc(usock->ipaddr, port);
+- atomic_set(&usock->wmem_peak, 0);
+- atomic_set(&usock->err_full, 0);
+- atomic_set(&usock->err_other, 0);
++ usock_connect(usock, 0);
+ add_usock(usock);
+ } else
+ break;
+@@ -781,7 +1018,7 @@ static int add_aggregation(char *ptr)
+ LIST_HEAD(old_aggr_list);
+
+ while (ptr && *ptr) {
+- unsigned char ip[4];
++ unsigned char ip[4];
+ unsigned int mask;
+ unsigned int port1, port2;
+ unsigned int aggr_to;
+@@ -792,16 +1029,16 @@ static int add_aggregation(char *ptr)
+ ip, ip + 1, ip + 2, ip + 3, &mask, &aggr_to) == 6) {
+
+ if (!(aggr_n = vmalloc(sizeof(*aggr_n)))) {
+- printk(KERN_ERR "netflow: can't vmalloc aggr\n");
++ printk(KERN_ERR "ipt_NETFLOW: can't vmalloc aggr\n");
+ return -ENOMEM;
+ }
+ memset(aggr_n, 0, sizeof(*aggr_n));
+
+- aggr_n->addr = ntohl(*(__be32 *)ip);
+ aggr_n->mask = bits2mask(mask);
++ aggr_n->addr = ntohl(*(__be32 *)ip) & aggr_n->mask;
+ aggr_n->aggr_mask = bits2mask(aggr_to);
+ aggr_n->prefix = mask;
+- printk(KERN_INFO "netflow: add aggregation [%u.%u.%u.%u/%u=%u]\n",
++ printk(KERN_INFO "ipt_NETFLOW: add aggregation [%u.%u.%u.%u/%u=%u]\n",
+ HIPQUAD(aggr_n->addr), mask, aggr_to);
+ list_add_tail(&aggr_n->list, &new_aggr_n_list);
+
+@@ -809,7 +1046,7 @@ static int add_aggregation(char *ptr)
+ sscanf(ptr, "%u=%u", &port2, &aggr_to) == 2) {
+
+ if (!(aggr_p = vmalloc(sizeof(*aggr_p)))) {
+- printk(KERN_ERR "netflow: can't vmalloc aggr\n");
++ printk(KERN_ERR "ipt_NETFLOW: can't vmalloc aggr\n");
+ return -ENOMEM;
+ }
+ memset(aggr_p, 0, sizeof(*aggr_p));
+@@ -817,11 +1054,11 @@ static int add_aggregation(char *ptr)
+ aggr_p->port1 = port1;
+ aggr_p->port2 = port2;
+ aggr_p->aggr_port = aggr_to;
+- printk(KERN_INFO "netflow: add aggregation [%u-%u=%u]\n",
++ printk(KERN_INFO "ipt_NETFLOW: add aggregation [%u-%u=%u]\n",
+ port1, port2, aggr_to);
+ list_add_tail(&aggr_p->list, &new_aggr_p_list);
+ } else {
+- printk(KERN_ERR "netflow: bad aggregation rule: %s (ignoring)\n", ptr);
++ printk(KERN_ERR "ipt_NETFLOW: bad aggregation rule: %s (ignoring)\n", ptr);
+ break;
+ }
+
+@@ -846,17 +1083,23 @@ static int add_aggregation(char *ptr)
+
+ static inline u_int32_t hash_netflow(const struct ipt_netflow_tuple *tuple)
+ {
+- /* tuple is rounded to u32s */
+- return jhash2((u32 *)tuple, NETFLOW_TUPLE_SIZE, ipt_netflow_hash_rnd) % ipt_netflow_hash_size;
++ return murmur3(tuple, sizeof(struct ipt_netflow_tuple), ipt_netflow_hash_rnd) % ipt_netflow_hash_size;
+ }
+
+ static struct ipt_netflow *
+-ipt_netflow_find(const struct ipt_netflow_tuple *tuple, unsigned int hash)
++ipt_netflow_find(const struct ipt_netflow_tuple *tuple, const unsigned int hash)
+ {
+ struct ipt_netflow *nf;
++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,9,0)
++#define compat_hlist_for_each_entry hlist_for_each_entry
++#define compat_hlist_for_each_entry_safe hlist_for_each_entry_safe
+ struct hlist_node *pos;
++#else /* since 3.9.0 */
++#define compat_hlist_for_each_entry(a,pos,c,d) hlist_for_each_entry(a,c,d)
++#define compat_hlist_for_each_entry_safe(a,pos,c,d,e) hlist_for_each_entry_safe(a,c,d,e)
++#endif
+
+- hlist_for_each_entry(nf, pos, &ipt_netflow_hash[hash], hlist) {
++ compat_hlist_for_each_entry(nf, pos, &ipt_netflow_hash[hash], hlist) {
+ if (ipt_netflow_tuple_equal(tuple, &nf->tuple) &&
+ nf->nr_bytes < FLOW_FULL_WATERMARK) {
+ NETFLOW_STAT_INC(found);
+@@ -868,7 +1111,7 @@ ipt_netflow_find(const struct ipt_netflow_tuple *tuple, unsigned int hash)
+ return NULL;
+ }
+
+-static struct hlist_head *alloc_hashtable(int size)
++static struct hlist_head *alloc_hashtable(const int size)
+ {
+ struct hlist_head *hash;
+
+@@ -879,19 +1122,18 @@ static struct hlist_head *alloc_hashtable(int size)
+ for (i = 0; i < size; i++)
+ INIT_HLIST_HEAD(&hash[i]);
+ } else
+- printk(KERN_ERR "netflow: unable to vmalloc hash table.\n");
++ printk(KERN_ERR "ipt_NETFLOW: unable to vmalloc hash table.\n");
+
+ return hash;
+ }
+
+-static int set_hashsize(int new_size)
++static int set_hashsize(const int new_size)
+ {
+ struct hlist_head *new_hash, *old_hash;
+- unsigned int hash;
+ struct ipt_netflow *nf;
+ int rnd;
+
+- printk(KERN_INFO "netflow: allocating new hash table %u -> %u buckets\n",
++ printk(KERN_INFO "ipt_NETFLOW: allocating new hash table %u -> %u buckets\n",
+ ipt_netflow_hash_size, new_size);
+ new_hash = alloc_hashtable(new_size);
+ if (!new_hash)
+@@ -900,19 +1142,24 @@ static int set_hashsize(int new_size)
+ get_random_bytes(&rnd, 4);
+
+ /* rehash */
+- spin_lock_bh(&ipt_netflow_lock);
++ write_lock_bh(&htable_rwlock);
+ old_hash = ipt_netflow_hash;
+ ipt_netflow_hash = new_hash;
+ ipt_netflow_hash_size = new_size;
+ ipt_netflow_hash_rnd = rnd;
+ /* hash_netflow() is dependent on ipt_netflow_hash_* values */
++ spin_lock(&hlist_lock);
+ list_for_each_entry(nf, &ipt_netflow_list, list) {
++ unsigned int hash;
++
+ hash = hash_netflow(&nf->tuple);
+ /* hlist_add_head overwrites hlist pointers for this node
+ * so it's good */
+ hlist_add_head(&nf->hlist, &new_hash[hash]);
++ nf->lock = &htable_locks[hash & LOCK_COUNT_MASK];
+ }
+- spin_unlock_bh(&ipt_netflow_lock);
++ spin_unlock(&hlist_lock);
++ write_unlock_bh(&htable_rwlock);
+
+ vfree(old_hash);
+
+@@ -920,14 +1167,14 @@ static int set_hashsize(int new_size)
+ }
+
+ static struct ipt_netflow *
+-ipt_netflow_alloc(struct ipt_netflow_tuple *tuple)
++ipt_netflow_alloc(const struct ipt_netflow_tuple *tuple)
+ {
+ struct ipt_netflow *nf;
+ long count;
+
+ nf = kmem_cache_alloc(ipt_netflow_cachep, GFP_ATOMIC);
+ if (!nf) {
+- printk(KERN_ERR "Can't allocate netflow.\n");
++ printk(KERN_ERR "ipt_NETFLOW: Can't allocate flow.\n");
+ return NULL;
+ }
+
+@@ -945,13 +1192,15 @@ ipt_netflow_alloc(struct ipt_netflow_tuple *tuple)
+
+ static void ipt_netflow_free(struct ipt_netflow *nf)
+ {
++ if (IS_DUMMY_FLOW(nf))
++ return;
+ atomic_dec(&ipt_netflow_count);
+ kmem_cache_free(ipt_netflow_cachep, nf);
+ }
+
+ static struct ipt_netflow *
+-init_netflow(struct ipt_netflow_tuple *tuple,
+- struct sk_buff *skb, unsigned int hash)
++init_netflow(const struct ipt_netflow_tuple *tuple,
++ const struct sk_buff *skb, const unsigned int hash)
+ {
+ struct ipt_netflow *nf;
+
+@@ -959,93 +1208,774 @@ init_netflow(struct ipt_netflow_tuple *tuple,
+ if (!nf)
+ return NULL;
+
++ nf->lock = &htable_locks[hash & LOCK_COUNT_MASK];
+ hlist_add_head(&nf->hlist, &ipt_netflow_hash[hash]);
++ spin_lock(&hlist_lock);
+ list_add(&nf->list, &ipt_netflow_list);
++ spin_unlock(&hlist_lock);
+
+ return nf;
+ }
+
+ /* cook pdu, send, and clean */
+ /* only called in scan worker path */
+-static void netflow_export_pdu(void)
++static void netflow_export_pdu_v5(void)
+ {
+ struct timeval tv;
+ int pdusize;
+
+- if (!pdu.nr_records)
++ if (!pdu_data_records)
+ return;
+
+ if (debug > 1)
+- printk(KERN_INFO "netflow_export_pdu with %d records\n", pdu.nr_records);
+- do_gettimeofday(&tv);
+-
+- pdu.version = htons(5);
+- pdu.ts_uptime = htonl(jiffies_to_msecs(jiffies));
+- pdu.ts_usecs = htonl(tv.tv_sec);
+- pdu.ts_unsecs = htonl(tv.tv_usec);
+- //pdu.eng_type = 0;
+- //pdu.eng_id = 0;
+- //pdu.padding = 0;
++ printk(KERN_INFO "netflow_export_pdu_v5 with %d records\n", pdu_data_records);
+
+- pdusize = NETFLOW5_HEADER_SIZE + sizeof(struct netflow5_record) * pdu.nr_records;
+-
+- /* especially fix nr_records before export */
+- pdu.nr_records = htons(pdu.nr_records);
++ pdu.v5.version = htons(5);
++ pdu.v5.nr_records = htons(pdu_data_records);
++ pdu.v5.ts_uptime = htonl(jiffies_to_msecs(jiffies));
++ do_gettimeofday(&tv);
++ pdu.v5.ts_usecs = htonl(tv.tv_sec);
++ pdu.v5.ts_unsecs = htonl(tv.tv_usec);
++ pdu.v5.seq = htonl(pdu_seq);
++ //pdu.v5.eng_type = 0;
++ pdu.v5.eng_id = engine_id;
++ //pdu.v5.padding = 0;
+
+- if (netflow_send_pdu(&pdu, pdusize) == 0) {
+- /* not least one send succeded, account stat for dropped packets */
+- NETFLOW_STAT_ADD_ATOMIC(pkt_drop, pdu_packets);
+- NETFLOW_STAT_ADD_ATOMIC(traf_drop, pdu_traf);
+- }
++ pdusize = NETFLOW5_HEADER_SIZE + sizeof(struct netflow5_record) * pdu_data_records;
+
+- pdu.seq = htonl(ntohl(pdu.seq) + ntohs(pdu.nr_records));
++ netflow_sendmsg(&pdu.v5, pdusize);
+
+- pdu.nr_records = 0;
+ pdu_packets = 0;
+- pdu_traf = 0;
++ pdu_traf = 0;
++
++ pdu_seq += pdu_data_records;
++ pdu_count++;
++ pdu_data_records = 0;
+ }
+
+ /* only called in scan worker path */
+-static void netflow_export_flow(struct ipt_netflow *nf)
++static void netflow_export_flow_v5(struct ipt_netflow *nf)
+ {
+ struct netflow5_record *rec;
+
+- if (debug > 2)
+- printk(KERN_INFO "adding flow to export (%d)\n", pdu.nr_records);
++ if (unlikely(debug > 2))
++ printk(KERN_INFO "adding flow to export (%d)\n", pdu_data_records);
+
+ pdu_packets += nf->nr_packets;
+ pdu_traf += nf->nr_bytes;
+ pdu_ts_mod = jiffies;
+- rec = &pdu.flow[pdu.nr_records++];
++ rec = &pdu.v5.flow[pdu_data_records++];
+
+ /* make V5 flow record */
+- rec->s_addr = nf->tuple.s_addr;
+- rec->d_addr = nf->tuple.d_addr;
+- //rec->nexthop = 0;
++ rec->s_addr = nf->tuple.src.ip;
++ rec->d_addr = nf->tuple.dst.ip;
++ rec->nexthop = nf->nh.ip;
+ rec->i_ifc = htons(nf->tuple.i_ifc);
+ rec->o_ifc = htons(nf->o_ifc);
+ rec->nr_packets = htonl(nf->nr_packets);
+ rec->nr_octets = htonl(nf->nr_bytes);
+- rec->ts_first = htonl(jiffies_to_msecs(nf->ts_first));
+- rec->ts_last = htonl(jiffies_to_msecs(nf->ts_last));
++ rec->first_ms = htonl(jiffies_to_msecs(nf->ts_first));
++ rec->last_ms = htonl(jiffies_to_msecs(nf->ts_last));
+ rec->s_port = nf->tuple.s_port;
+ rec->d_port = nf->tuple.d_port;
+- //rec->reserved = 0;
++ //rec->reserved = 0; /* pdu is always zeroized for v5 in netflow_switch_version */
+ rec->tcp_flags = nf->tcp_flags;
+ rec->protocol = nf->tuple.protocol;
+ rec->tos = nf->tuple.tos;
+- //rec->s_as = 0;
+- //rec->d_as = 0;
++#ifdef CONFIG_NF_NAT_NEEDED
++ rec->s_as = nf->s_as;
++ rec->d_as = nf->d_as;
++#endif
+ rec->s_mask = nf->s_mask;
+ rec->d_mask = nf->d_mask;
+ //rec->padding = 0;
+ ipt_netflow_free(nf);
+
+- if (pdu.nr_records == NETFLOW5_RECORDS_MAX)
++ if (pdu_data_records == NETFLOW5_RECORDS_MAX)
++ netflow_export_pdu_v5();
++}
++
++/* pdu is initially blank, export current pdu, and prepare next for filling. */
++static void netflow_export_pdu_v9(void)
++{
++ struct timeval tv;
++ int pdusize;
++
++ if (pdu_data_used <= pdu.v9.data)
++ return;
++
++ if (debug > 1)
++ printk(KERN_INFO "netflow_export_pdu_v9 with %d records\n",
++ pdu_data_records + pdu_tpl_records);
++
++ pdu.v9.version = htons(9);
++ pdu.v9.nr_records = htons(pdu_data_records + pdu_tpl_records);
++ pdu.v9.sys_uptime_ms = htonl(jiffies_to_msecs(jiffies));
++ do_gettimeofday(&tv);
++ pdu.v9.export_time_s = htonl(tv.tv_sec);
++ pdu.v9.seq = htonl(pdu_seq);
++ pdu.v9.source_id = engine_id;
++
++ pdusize = pdu_data_used - (unsigned char *)&pdu.v9;
++
++ netflow_sendmsg(&pdu.v9, pdusize);
++
++ pdu_packets = 0;
++ pdu_traf = 0;
++
++ pdu_seq++;
++ pdu_count++;
++ pdu_data_records = pdu_tpl_records = 0;
++ pdu_data_used = pdu.v9.data;
++ pdu_flowset = NULL;
++}
++
++static void netflow_export_pdu_ipfix(void)
++{
++ struct timeval tv;
++ int pdusize;
++
++ if (pdu_data_used <= pdu.ipfix.data)
++ return;
++
++ if (debug > 1)
++ printk(KERN_INFO "netflow_export_pduX with %d records\n",
++ pdu_data_records);
++
++ pdu.ipfix.version = htons(10);
++ do_gettimeofday(&tv);
++ pdu.ipfix.export_time_s = htonl(tv.tv_sec);
++ pdu.ipfix.seq = htonl(pdu_seq);
++ pdu.ipfix.odomain_id = engine_id;
++ pdusize = pdu_data_used - (unsigned char *)&pdu;
++ pdu.ipfix.length = htons(pdusize);
++
++ netflow_sendmsg(&pdu.ipfix, pdusize);
++
++ pdu_packets = 0;
++ pdu_traf = 0;
++
++ pdu_seq += pdu_data_records;
++ pdu_count++;
++ pdu_data_records = pdu_tpl_records = 0;
++ pdu_data_used = pdu.ipfix.data;
++ pdu_flowset = NULL;
++}
++
++static inline int pdu_have_space(const size_t size)
++{
++ return ((pdu_data_used + size) <= pdu_high_wm);
++}
++
++static inline unsigned char *pdu_grab_space(const size_t size)
++{
++ unsigned char *ptr = pdu_data_used;
++ pdu_data_used += size;
++ return ptr;
++}
++
++// allocate data space in pdu, or fail if pdu is reallocated.
++static inline unsigned char *pdu_alloc_fail(const size_t size)
++{
++ if (!pdu_have_space(size)) {
+ netflow_export_pdu();
++ return NULL;
++ }
++ return pdu_grab_space(size);
++}
++
++/* doesn't fail, but can provide empty pdu. */
++static unsigned char *pdu_alloc(const size_t size)
++{
++ return pdu_alloc_fail(size) ?: pdu_grab_space(size);
++}
++
++/* global table of sizes of template field types */
++static u_int8_t tpl_element_sizes[] = {
++ [IN_BYTES] = 4,
++ [IN_PKTS] = 4,
++ [PROTOCOL] = 1,
++ [TOS] = 1,
++ [TCP_FLAGS] = 1,
++ [L4_SRC_PORT] = 2,
++ [IPV4_SRC_ADDR] = 4,
++ [SRC_MASK] = 1,
++ [INPUT_SNMP] = 2,
++ [L4_DST_PORT] = 2,
++ [IPV4_DST_ADDR] = 4,
++ [DST_MASK] = 1,
++ [OUTPUT_SNMP] = 2,
++ [IPV4_NEXT_HOP] = 4,
++ //[SRC_AS] = 2,
++ //[DST_AS] = 2,
++ //[BGP_IPV4_NEXT_HOP] = 4,
++ //[MUL_DST_PKTS] = 4,
++ //[MUL_DST_BYTES] = 4,
++ [LAST_SWITCHED] = 4,
++ [FIRST_SWITCHED]= 4,
++ [IPV6_SRC_ADDR] = 16,
++ [IPV6_DST_ADDR] = 16,
++ [IPV6_FLOW_LABEL] = 3,
++ [ICMP_TYPE] = 2,
++ [MUL_IGMP_TYPE] = 1,
++ //[TOTAL_BYTES_EXP] = 4,
++ //[TOTAL_PKTS_EXP] = 4,
++ //[TOTAL_FLOWS_EXP] = 4,
++ [IPV6_NEXT_HOP] = 16,
++ [IPV6_OPTION_HEADERS] = 2,
++ [commonPropertiesId] = 4,
++ [ipv4Options] = 4,
++ [tcpOptions] = 4,
++ [postNATSourceIPv4Address] = 4,
++ [postNATDestinationIPv4Address] = 4,
++ [postNAPTSourceTransportPort] = 2,
++ [postNAPTDestinationTransportPort] = 2,
++ [natEvent] = 1,
++ [postNATSourceIPv6Address] = 16,
++ [postNATDestinationIPv6Address] = 16,
++ [IPSecSPI] = 4,
++ [observationTimeMilliseconds] = 8,
++ [observationTimeMicroseconds] = 8,
++ [observationTimeNanoseconds] = 8,
++};
++
++#define TEMPLATES_HASH_BSIZE 8
++#define TEMPLATES_HASH_SIZE (1<<TEMPLATES_HASH_BSIZE)
++static struct hlist_head templates_hash[TEMPLATES_HASH_SIZE];
++
++struct base_template {
++ int length; /* number of elements in template */
++ u_int16_t types[]; /* {type, size} pairs */
++};
++
++/* base templates */
++#define BTPL_BASE 0x00000001 /* base stat */
++#define BTPL_IP4 0x00000002 /* IPv4 */
++#define BTPL_MASK4 0x00000004 /* Aggregated */
++#define BTPL_PORTS 0x00000008 /* UDP&TCP */
++#define BTPL_IP6 0x00000010 /* IPv6 */
++#define BTPL_ICMP 0x00000020 /* ICMP */
++#define BTPL_IGMP 0x00000040 /* IGMP */
++#define BTPL_IPSEC 0x00000080 /* AH&ESP */
++#define BTPL_NAT4 0x00000100 /* NAT IPv4 */
++#define BTPL_MARK 0x00000400 /* connmark */
++#define BTPL_LABEL6 0x00000800 /* IPv6 flow label */
++#define BTPL_OPTIONS4 0x00001000 /* IPv4 Options */
++#define BTPL_OPTIONS6 0x00002000 /* IPv6 Options */
++#define BTPL_TCPOPTIONS 0x00004000 /* TCP Options */
++#define BTPL_MAX 32
++
++static struct base_template template_base = {
++ .types = {
++ INPUT_SNMP,
++ OUTPUT_SNMP,
++ IN_PKTS,
++ IN_BYTES,
++ FIRST_SWITCHED,
++ LAST_SWITCHED,
++ PROTOCOL,
++ TOS,
++ 0
++ }
++};
++static struct base_template template_ipv4 = {
++ .types = {
++ IPV4_SRC_ADDR,
++ IPV4_DST_ADDR,
++ IPV4_NEXT_HOP,
++ 0
++ }
++};
++static struct base_template template_options4 = {
++ .types = { ipv4Options, 0 }
++};
++static struct base_template template_tcpoptions = {
++ .types = { tcpOptions, 0 }
++};
++static struct base_template template_ipv6 = {
++ .types = {
++ IPV6_SRC_ADDR,
++ IPV6_DST_ADDR,
++ IPV6_NEXT_HOP,
++ 0
++ }
++};
++static struct base_template template_options6 = {
++ .types = { IPV6_OPTION_HEADERS, 0 }
++};
++static struct base_template template_label6 = {
++ .types = { IPV6_FLOW_LABEL, 0 }
++};
++static struct base_template template_ipv4_mask = {
++ .types = {
++ SRC_MASK,
++ DST_MASK,
++ 0
++ }
++};
++static struct base_template template_ports = {
++ .types = {
++ L4_SRC_PORT,
++ L4_DST_PORT,
++ TCP_FLAGS,
++ 0
++ }
++};
++static struct base_template template_icmp = {
++ .types = { ICMP_TYPE, 0 }
++};
++static struct base_template template_igmp = {
++ .types = { MUL_IGMP_TYPE, 0 }
++};
++static struct base_template template_ipsec = {
++ .types = { IPSecSPI, 0 }
++};
++static struct base_template template_nat4 = {
++ .types = {
++ observationTimeMilliseconds,
++ IPV4_SRC_ADDR,
++ IPV4_DST_ADDR,
++ postNATSourceIPv4Address,
++ postNATDestinationIPv4Address,
++ L4_SRC_PORT,
++ L4_DST_PORT,
++ postNAPTSourceTransportPort,
++ postNAPTDestinationTransportPort,
++ PROTOCOL,
++ natEvent,
++ 0
++ }
++};
++static struct base_template template_mark = {
++ .types = { commonPropertiesId, 0 }
++};
++
++struct data_template {
++ struct hlist_node hlist;
++ int tpl_mask;
++
++ int length; /* number of elements in template */
++ int tpl_size; /* summary size of template with flowset header */
++ int rec_size; /* summary size of all recods of template (w/o flowset header) */
++ int template_id_n; /* assigned from template_ids, network order. */
++ int exported_cnt;
++ unsigned long exported_ts; /* jiffies */
++ u_int16_t fields[]; /* {type, size} pairs */
++} __attribute__ ((packed));
++
++#define TPL_FIELD_NSIZE 4 /* one complete template field's network size */
++
++static void free_templates(void)
++{
++ int i;
++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,9,0)
++ struct hlist_node *pos;
++#endif
++ struct hlist_node *tmp;
++
++ for (i = 0; i < TEMPLATES_HASH_SIZE; i++) {
++ struct hlist_head *thead = &templates_hash[i];
++ struct data_template *tpl;
++
++ compat_hlist_for_each_entry_safe(tpl, pos, tmp, thead, hlist)
++ kfree(tpl);
++ INIT_HLIST_HEAD(thead);
++ }
++ tpl_count = 0;
++}
++
++/* create combined template from mask */
++static struct data_template *get_template(const int tmask)
++{
++ struct base_template *tlist[BTPL_MAX];
++ struct data_template *tpl;
++ int tnum;
++ int length;
++ int i, j, k;
++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,9,0)
++ struct hlist_node *pos;
++#endif
++ int hash = hash_long(tmask, TEMPLATES_HASH_BSIZE);
++
++ compat_hlist_for_each_entry(tpl, pos, &templates_hash[hash], hlist)
++ if (tpl->tpl_mask == tmask)
++ return tpl;
++
++ tnum = 0;
++ if (tmask & BTPL_IP4) {
++ tlist[tnum++] = &template_ipv4;
++ if (tmask & BTPL_OPTIONS4)
++ tlist[tnum++] = &template_options4;
++ if (tmask & BTPL_MASK4)
++ tlist[tnum++] = &template_ipv4_mask;
++ } else if (tmask & BTPL_IP6) {
++ tlist[tnum++] = &template_ipv6;
++ if (tmask & BTPL_LABEL6)
++ tlist[tnum++] = &template_label6;
++ if (tmask & BTPL_OPTIONS6)
++ tlist[tnum++] = &template_options6;
++ } else if (tmask & BTPL_NAT4)
++ tlist[tnum++] = &template_nat4;
++ if (tmask & BTPL_PORTS)
++ tlist[tnum++] = &template_ports;
++ if (tmask & BTPL_BASE)
++ tlist[tnum++] = &template_base;
++ if (tmask & BTPL_TCPOPTIONS)
++ tlist[tnum++] = &template_tcpoptions;
++ if (tmask & BTPL_ICMP)
++ tlist[tnum++] = &template_icmp;
++ if (tmask & BTPL_IGMP)
++ tlist[tnum++] = &template_igmp;
++ if (tmask & BTPL_IPSEC)
++ tlist[tnum++] = &template_ipsec;
++ if (tmask & BTPL_MARK)
++ tlist[tnum++] = &template_mark;
++
++ /* calc memory size */
++ length = 0;
++ for (i = 0; i < tnum; i++) {
++ if (!tlist[i]->length) {
++ for (k = 0; tlist[i]->types[k]; k++);
++ tlist[i]->length = k;
++ }
++ length += tlist[i]->length;
++ }
++ /* elements are pairs + one termiantor */
++ tpl = kmalloc(sizeof(struct data_template) + (length * 2 + 1) * sizeof(u_int16_t), GFP_KERNEL);
++ if (!tpl) {
++ printk(KERN_ERR "ipt_NETFLOW: unable to kmalloc template.\n");
++ return NULL;
++ }
++ tpl->tpl_mask = tmask;
++ tpl->length = length;
++ tpl->tpl_size = sizeof(struct flowset_template);
++ tpl->rec_size = 0;
++ tpl->template_id_n = htons(template_ids++);
++ tpl->exported_cnt = 0;
++ tpl->exported_ts = 0;
++
++ j = 0;
++ for (i = 0; i < tnum; i++) {
++ struct base_template *btpl = tlist[i];
++
++ for (k = 0; k < btpl->length; k++) {
++ int size;
++ int type = btpl->types[k];
++
++ tpl->fields[j++] = type;
++ size = tpl_element_sizes[type];
++ tpl->fields[j++] = size;
++ tpl->rec_size += size;
++ }
++ tpl->tpl_size += btpl->length * TPL_FIELD_NSIZE;
++ }
++ tpl->fields[j++] = 0;
++
++ hlist_add_head(&tpl->hlist, &templates_hash[hash]);
++ tpl_count++;
++
++ return tpl;
++}
++
++static void pdu_add_template(struct data_template *tpl)
++{
++ int i;
++ unsigned char *ptr;
++ struct flowset_template *ntpl;
++ __be16 *sptr;
++
++ ptr = pdu_alloc(tpl->tpl_size);
++ ntpl = (struct flowset_template *)ptr;
++ ntpl->flowset_id = protocol == 9? htons(FLOWSET_TEMPLATE) : htons(IPFIX_TEMPLATE);
++ ntpl->length = htons(tpl->tpl_size);
++ ntpl->template_id = tpl->template_id_n;
++ ntpl->field_count = htons(tpl->length);
++ ptr += sizeof(struct flowset_template);
++ sptr = (__be16 *)ptr;
++ for (i = 0; ; ) {
++ int type = tpl->fields[i++];
++ if (!type)
++ break;
++ *sptr++ = htons(type);
++ *sptr++ = htons(tpl->fields[i++]);
++ }
++
++ tpl->exported_cnt = pdu_count;
++ tpl->exported_ts = jiffies;
++
++ pdu_flowset = NULL;
++ pdu_tpl_records++;
++}
++
++#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)
++static inline s64 portable_ktime_to_ms(const ktime_t kt)
++{
++ struct timeval tv = ktime_to_timeval(kt);
++ return (s64) tv.tv_sec * MSEC_PER_SEC + tv.tv_usec / USEC_PER_MSEC;
++}
++#define ktime_to_ms portable_ktime_to_ms
++#endif
++
++/* encode one field */
++typedef struct in6_addr in6_t;
++static inline void add_ipv4_field(__u8 *ptr, const int type, const struct ipt_netflow *nf)
++{
++ switch (type) {
++ case IN_BYTES: *(__be32 *)ptr = htonl(nf->nr_bytes); break;
++ case IN_PKTS: *(__be32 *)ptr = htonl(nf->nr_packets); break;
++ case FIRST_SWITCHED: *(__be32 *)ptr = htonl(jiffies_to_msecs(nf->ts_first)); break;
++ case LAST_SWITCHED: *(__be32 *)ptr = htonl(jiffies_to_msecs(nf->ts_last)); break;
++ case IPV4_SRC_ADDR: *(__be32 *)ptr = nf->tuple.src.ip; break;
++ case IPV4_DST_ADDR: *(__be32 *)ptr = nf->tuple.dst.ip; break;
++ case IPV4_NEXT_HOP: *(__be32 *)ptr = nf->nh.ip; break;
++ case L4_SRC_PORT: *(__be16 *)ptr = nf->tuple.s_port; break;
++ case L4_DST_PORT: *(__be16 *)ptr = nf->tuple.d_port; break;
++ case INPUT_SNMP: *(__be16 *)ptr = htons(nf->tuple.i_ifc); break;
++ case OUTPUT_SNMP: *(__be16 *)ptr = htons(nf->o_ifc); break;
++ case PROTOCOL: *ptr = nf->tuple.protocol; break;
++ case TCP_FLAGS: *ptr = nf->tcp_flags; break;
++ case TOS: *ptr = nf->tuple.tos; break;
++ case IPV6_SRC_ADDR: *(in6_t *)ptr = nf->tuple.src.in6; break;
++ case IPV6_DST_ADDR: *(in6_t *)ptr = nf->tuple.dst.in6; break;
++ case IPV6_NEXT_HOP: *(in6_t *)ptr = nf->nh.in6; break;
++ case IPV6_FLOW_LABEL: *ptr++ = nf->flow_label >> 16;
++ *(__be16 *)ptr = nf->flow_label;
++ break;
++ case tcpOptions: *(__be32 *)ptr = htonl(nf->tcpoptions); break;
++ case ipv4Options: *(__be32 *)ptr = htonl(nf->options); break;
++ case IPV6_OPTION_HEADERS: *(__be16 *)ptr = htons(nf->options); break;
++#ifdef CONFIG_NF_CONNTRACK_MARK
++ case commonPropertiesId:
++ *(__be32 *)ptr = htonl(nf->mark); break;
++#endif
++ case SRC_MASK: *ptr = nf->s_mask; break;
++ case DST_MASK: *ptr = nf->d_mask; break;
++ case ICMP_TYPE: *(__be16 *)ptr = nf->tuple.d_port; break;
++ case MUL_IGMP_TYPE: *ptr = nf->tuple.d_port; break;
++#ifdef CONFIG_NF_NAT_NEEDED
++ case postNATSourceIPv4Address: *(__be32 *)ptr = nf->nat->post.s_addr; break;
++ case postNATDestinationIPv4Address: *(__be32 *)ptr = nf->nat->post.d_addr; break;
++ case postNAPTSourceTransportPort: *(__be16 *)ptr = nf->nat->post.s_port; break;
++ case postNAPTDestinationTransportPort: *(__be16 *)ptr = nf->nat->post.d_port; break;
++ case natEvent: *ptr = nf->nat->nat_event; break;
++#endif
++ case IPSecSPI: *(__u32 *)ptr = (nf->tuple.s_port << 16) | nf->tuple.d_port; break;
++ case observationTimeMilliseconds:
++ *(__be64 *)ptr = cpu_to_be64(ktime_to_ms(nf->ts_obs)); break;
++ case observationTimeMicroseconds:
++ *(__be64 *)ptr = cpu_to_be64(ktime_to_us(nf->ts_obs)); break;
++ case observationTimeNanoseconds:
++ *(__be64 *)ptr = cpu_to_be64(ktime_to_ns(nf->ts_obs)); break;
++ default:
++ memset(ptr, 0, tpl_element_sizes[type]);
++ }
++}
++
++#define PAD_SIZE 4 /* rfc prescribes flowsets to be padded */
++
++/* cache timeout_rate in jiffies */
++static inline unsigned long timeout_rate_j(void)
++{
++ static unsigned int t_rate = 0;
++ static unsigned long t_rate_j = 0;
++
++ if (unlikely(timeout_rate != t_rate)) {
++ struct timeval tv = { .tv_sec = timeout_rate * 60, .tv_usec = 0 };
++
++ t_rate = timeout_rate;
++ t_rate_j = timeval_to_jiffies(&tv);
++ }
++ return t_rate_j;
++}
++
++#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
++#define IPPROTO_UDPLITE 136
++#endif
++
++#ifndef time_is_before_jiffies
++#define time_is_before_jiffies(a) time_after(jiffies, a)
++#endif
++
++static void netflow_export_flow_tpl(struct ipt_netflow *nf)
++{
++ unsigned char *ptr;
++ int i;
++ struct data_template *tpl;
++ int tpl_mask = BTPL_BASE;
++
++ if (unlikely(debug > 2))
++ printk(KERN_INFO "adding flow to export (%d)\n",
++ pdu_data_records + pdu_tpl_records);
++
++ if (likely(nf->tuple.l3proto == AF_INET)) {
++ tpl_mask |= BTPL_IP4;
++ if (unlikely(nf->options))
++ tpl_mask |= BTPL_OPTIONS4;
++ } else {
++ tpl_mask |= BTPL_IP6;
++ if (unlikely(nf->options))
++ tpl_mask |= BTPL_OPTIONS6;
++ if (unlikely(nf->flow_label))
++ tpl_mask |= BTPL_LABEL6;
++ }
++ if (unlikely(nf->tcpoptions))
++ tpl_mask |= BTPL_TCPOPTIONS;
++ if (unlikely(nf->s_mask || nf->d_mask))
++ tpl_mask |= BTPL_MASK4;
++ if (likely(nf->tuple.protocol == IPPROTO_TCP ||
++ nf->tuple.protocol == IPPROTO_UDP ||
++ nf->tuple.protocol == IPPROTO_SCTP ||
++ nf->tuple.protocol == IPPROTO_UDPLITE))
++ tpl_mask |= BTPL_PORTS;
++ else if (nf->tuple.protocol == IPPROTO_ICMP)
++ tpl_mask |= BTPL_ICMP;
++ else if (nf->tuple.protocol == IPPROTO_IGMP)
++ tpl_mask |= BTPL_IGMP;
++#ifdef CONFIG_NF_CONNTRACK_MARK
++ if (nf->mark)
++ tpl_mask |= BTPL_MARK;
++#endif
++#ifdef CONFIG_NF_NAT_NEEDED
++ if (nf->nat)
++ tpl_mask = BTPL_NAT4;
++#endif
++
++ tpl = get_template(tpl_mask);
++ if (unlikely(!tpl)) {
++ printk(KERN_INFO "ipt_NETFLOW: template allocation failed.\n");
++ NETFLOW_STAT_INC(alloc_err);
++ NETFLOW_STAT_ADD_ATOMIC(pkt_drop, nf->nr_packets);
++ NETFLOW_STAT_ADD_ATOMIC(traf_drop, nf->nr_bytes);
++ ipt_netflow_free(nf);
++ return;
++ }
++
++ if (unlikely(!pdu_flowset ||
++ pdu_flowset->flowset_id != tpl->template_id_n ||
++ !(ptr = pdu_alloc_fail(tpl->rec_size)))) {
++
++ /* if there was previous data template we should pad it to 4 bytes */
++ if (pdu_flowset) {
++ int padding = (PAD_SIZE - ntohs(pdu_flowset->length) % PAD_SIZE) % PAD_SIZE;
++ if (padding && (ptr = pdu_alloc_fail(padding))) {
++ pdu_flowset->length = htons(ntohs(pdu_flowset->length) + padding);
++ for (; padding; padding--)
++ *ptr++ = 0;
++ }
++ }
++
++ if (!tpl->exported_ts ||
++ pdu_count > (tpl->exported_cnt + refresh_rate) ||
++ time_is_before_jiffies(tpl->exported_ts + timeout_rate_j())) {
++ pdu_add_template(tpl);
++ }
++
++ ptr = pdu_alloc(sizeof(struct flowset_data) + tpl->rec_size);
++ pdu_flowset = (struct flowset_data *)ptr;
++ pdu_flowset->flowset_id = tpl->template_id_n;
++ pdu_flowset->length = htons(sizeof(struct flowset_data));
++ ptr += sizeof(struct flowset_data);
++ }
++
++ /* encode all fields */
++ for (i = 0; ; ) {
++ int type = tpl->fields[i++];
++
++ if (!type)
++ break;
++ add_ipv4_field(ptr, type, nf);
++ ptr += tpl->fields[i++];
++ }
++
++ pdu_data_records++;
++ pdu_flowset->length = htons(ntohs(pdu_flowset->length) + tpl->rec_size);
++
++ pdu_packets += nf->nr_packets;
++ pdu_traf += nf->nr_bytes;
++
++ ipt_netflow_free(nf);
++ pdu_ts_mod = jiffies;
++}
++
++static void netflow_switch_version(const int ver)
++{
++ protocol = ver;
++ if (protocol == 5) {
++ memset(&pdu, 0, sizeof(pdu));
++ netflow_export_flow = &netflow_export_flow_v5;
++ netflow_export_pdu = &netflow_export_pdu_v5;
++ } else if (protocol == 9) {
++ pdu_data_used = pdu.v9.data;
++ pdu_max_size = sizeof(pdu.v9);
++ pdu_high_wm = (unsigned char *)&pdu + pdu_max_size;
++ netflow_export_flow = &netflow_export_flow_tpl;
++ netflow_export_pdu = &netflow_export_pdu_v9;
++ } else { /* IPFIX */
++ pdu_data_used = pdu.ipfix.data;
++ pdu_max_size = sizeof(pdu.ipfix);
++ pdu_high_wm = (unsigned char *)&pdu + pdu_max_size;
++ netflow_export_flow = &netflow_export_flow_tpl;
++ netflow_export_pdu = &netflow_export_pdu_ipfix;
++ }
++ if (protocol != 5)
++ free_templates();
++ pdu_data_records = pdu_tpl_records = 0;
++ pdu_flowset = NULL;
++ printk(KERN_INFO "ipt_NETFLOW protocol version %d (%s) enabled.\n",
++ protocol, protocol == 10? "IPFIX" : "NetFlow");
++}
++
++#ifdef CONFIG_NF_NAT_NEEDED
++static void export_nat_event(struct nat_event *nel)
++{
++ static struct ipt_netflow nf = { { NULL } };
++
++ nf.tuple.l3proto = AF_INET;
++ nf.tuple.protocol = nel->protocol;
++ nf.nat = nel; /* this is also flag of dummy flow */
++ nf.tcp_flags = (nel->nat_event == NAT_DESTROY)? TCP_FIN_RST : TCP_SYN_ACK;
++ if (protocol >= 9) {
++ nf.ts_obs = nel->ts_ktime;
++ nf.tuple.src.ip = nel->pre.s_addr;
++ nf.tuple.dst.ip = nel->pre.d_addr;
++ nf.tuple.s_port = nel->pre.s_port;
++ nf.tuple.d_port = nel->pre.d_port;
++ netflow_export_flow(&nf);
++ } else { /* v5 */
++ /* The weird v5 packet(s).
++ * src and dst will be same as in data flow from the FORWARD chain
++ * where src is pre-nat src ip and dst is post-nat dst ip.
++ * What we lacking here is external src ip for SNAT, or
++ * pre-nat dst ip for DNAT. We will put this into Nexthop field
++ * with port into src/dst AS field. tcp_flags will distinguish it's
++ * start or stop event. Two flows in case of full nat. */
++ nf.tuple.src.ip = nel->pre.s_addr;
++ nf.tuple.s_port = nel->pre.s_port;
++ nf.tuple.dst.ip = nel->post.d_addr;
++ nf.tuple.d_port = nel->post.d_port;
++
++ nf.ts_first = nel->ts_jiffies;
++ nf.ts_last = nel->ts_jiffies;
++ if (nel->pre.s_addr != nel->post.s_addr ||
++ nel->pre.s_port != nel->post.s_port) {
++ nf.nh.ip = nel->post.s_addr;
++ nf.s_as = nel->post.s_port;
++ nf.d_as = 0;
++ netflow_export_flow(&nf);
++ }
++ if (nel->pre.d_addr != nel->post.d_addr ||
++ nel->pre.d_port != nel->post.d_port) {
++ nf.nh.ip = nel->pre.d_addr;
++ nf.s_as = 0;
++ nf.d_as = nel->pre.d_port;
++ netflow_export_flow(&nf);
++ }
++ }
++ kfree(nel);
+ }
++#endif /* CONFIG_NF_NAT_NEEDED */
+
+-static inline int active_needs_export(struct ipt_netflow *nf, long a_timeout)
++static inline int active_needs_export(const struct ipt_netflow *nf, const long a_timeout)
+ {
+ /* active too long, finishing, or having too much bytes */
+ return ((jiffies - nf->ts_first) > a_timeout) ||
+@@ -1057,42 +1987,77 @@ static inline int active_needs_export(struct ipt_netflow *nf, long a_timeout)
+
+ /* could be called with zero to flush cache and pdu */
+ /* this function is guaranteed to be called non-concurrently */
+-static void netflow_scan_and_export(int flush)
++/* return -1 is trylockfailed, 0 if nothin gexported, >=1 if exported something */
++static int netflow_scan_and_export(const int flush)
+ {
+ long i_timeout = inactive_timeout * HZ;
+ long a_timeout = active_timeout * HZ;
++ int trylock_failed = 0;
++ int pdu_c = pdu_count;
+
+ if (flush)
+ i_timeout = 0;
+
+- spin_lock_bh(&ipt_netflow_lock);
+- while (!list_empty(&ipt_netflow_list)) {
++ local_bh_disable();
++ spin_lock(&hlist_lock);
++ /* This is different order of locking than elsewhere,
++ * so we trylock&break to avoid deadlock. */
++
++ while (likely(!list_empty(&ipt_netflow_list))) {
+ struct ipt_netflow *nf;
+-
++
++ /* Last entry, which is usually oldest. */
+ nf = list_entry(ipt_netflow_list.prev, struct ipt_netflow, list);
++ if (!spin_trylock(nf->lock)) {
++ trylock_failed = 1;
++ break;
++ }
+ /* Note: i_timeout checked with >= to allow specifying zero timeout
+ * to purge all flows on module unload */
+ if (((jiffies - nf->ts_last) >= i_timeout) ||
+ active_needs_export(nf, a_timeout)) {
+ hlist_del(&nf->hlist);
++ spin_unlock(nf->lock);
++
+ list_del(&nf->list);
++ spin_unlock(&hlist_lock);
++ local_bh_enable();
++
+ NETFLOW_STAT_ADD(pkt_out, nf->nr_packets);
+ NETFLOW_STAT_ADD(traf_out, nf->nr_bytes);
+- spin_unlock_bh(&ipt_netflow_lock);
+ netflow_export_flow(nf);
+- spin_lock_bh(&ipt_netflow_lock);
++
++ local_bh_disable();
++ spin_lock(&hlist_lock);
+ } else {
++ spin_unlock(nf->lock);
+ /* all flows which need to be exported is always at the tail
+ * so if no more exportable flows we can break */
+ break;
+ }
+ }
+- spin_unlock_bh(&ipt_netflow_lock);
+-
++ spin_unlock(&hlist_lock);
++ local_bh_enable();
++
++#ifdef CONFIG_NF_NAT_NEEDED
++ spin_lock_bh(&nat_lock);
++ while (!list_empty(&nat_list)) {
++ struct nat_event *nel;
++
++ nel = list_entry(nat_list.next, struct nat_event, list);
++ list_del(&nel->list);
++ spin_unlock_bh(&nat_lock);
++ export_nat_event(nel);
++ spin_lock_bh(&nat_lock);
++ }
++ spin_unlock_bh(&nat_lock);
++#endif
+ /* flush flows stored in pdu if there no new flows for too long */
+ /* Note: using >= to allow flow purge on zero timeout */
+ if ((jiffies - pdu_ts_mod) >= i_timeout)
+ netflow_export_pdu();
++
++ return trylock_failed? -1 : pdu_count - pdu_c;
+ }
+
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20)
+@@ -1101,8 +2066,10 @@ static void netflow_work_fn(void *dummy)
+ static void netflow_work_fn(struct work_struct *dummy)
+ #endif
+ {
+- netflow_scan_and_export(0);
+- __start_scan_worker();
++ int status;
++
++ status = netflow_scan_and_export(DONT_FLUSH);
++ _schedule_scan_worker(status);
+ }
+
+ #define RATESHIFT 2
+@@ -1154,7 +2121,7 @@ static void rate_timer_calc(unsigned long dummy)
+ old_found = found;
+ old_notfound = notfound;
+ /* if there is no access to hash keep rate steady */
+- metric = (dfnd + dnfnd)? 10 * (dsrch + dfnd + dnfnd) / (dfnd + dnfnd) : metric;
++ metric = (dfnd + dnfnd)? 100 * (dsrch + dfnd + dnfnd) / (dfnd + dnfnd) : metric;
+ CALC_RATE(min15_metric, (unsigned long long)metric, 15);
+ CALC_RATE(min5_metric, (unsigned long long)metric, 5);
+ CALC_RATE(min_metric, (unsigned long long)metric, 1);
+@@ -1162,6 +2129,262 @@ static void rate_timer_calc(unsigned long dummy)
+ mod_timer(&rate_timer, jiffies + (HZ * SAMPLERATE));
+ }
+
++#ifdef CONFIG_NF_NAT_NEEDED
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
++static struct nf_ct_event_notifier *saved_event_cb __read_mostly = NULL;
++static int netflow_conntrack_event(const unsigned int events, struct nf_ct_event *item)
++#else
++static int netflow_conntrack_event(struct notifier_block *this, unsigned long events, void *ptr)
++#endif
++{
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
++ struct nf_conn *ct = item->ct;
++#else
++ struct nf_conn *ct = (struct nf_conn *)ptr;
++#endif
++ struct nat_event *nel;
++ const struct nf_conntrack_tuple *t;
++ int ret = NOTIFY_DONE;
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
++ struct nf_ct_event_notifier *notifier;
++
++ /* Call netlink first. */
++ notifier = rcu_dereference(saved_event_cb);
++ if (likely(notifier))
++ ret = notifier->fcn(events, item);
++#endif
++ if (unlikely(!natevents))
++ return ret;
++
++ if (!(events & ((1 << IPCT_NEW) | (1 << IPCT_RELATED) | (1 << IPCT_DESTROY))))
++ return ret;
++
++ if (!(ct->status & IPS_NAT_MASK))
++ return ret;
++
++ if (unlikely(ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple.src.l3num != AF_INET ||
++ ct->tuplehash[IP_CT_DIR_REPLY].tuple.src.l3num != AF_INET)) {
++ /* Well, there is no linux NAT for IPv6 anyway. */
++ return ret;
++ }
++
++ if (!(nel = kmalloc(sizeof(struct nat_event), GFP_ATOMIC))) {
++ printk(KERN_ERR "ipt_NETFLOW: can't kmalloc nat event\n");
++ return ret;
++ }
++ memset(nel, 0, sizeof(struct nat_event));
++ nel->ts_ktime = ktime_get_real();
++ nel->ts_jiffies = jiffies;
++ t = &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple;
++ nel->protocol = t->dst.protonum;
++ nel->pre.s_addr = t->src.u3.ip;
++ nel->pre.d_addr = t->dst.u3.ip;
++ nel->pre.s_port = t->src.u.all;
++ nel->pre.d_port = t->dst.u.all;
++ t = &ct->tuplehash[IP_CT_DIR_REPLY].tuple;
++ /* reply is reversed */
++ nel->post.s_addr = t->dst.u3.ip;
++ nel->post.d_addr = t->src.u3.ip;
++ nel->post.s_port = t->dst.u.all;
++ nel->post.d_port = t->src.u.all;
++ if (events & (1 << IPCT_DESTROY)) {
++ nel->nat_event = NAT_DESTROY;
++ nat_events_stop++;
++ } else {
++ nel->nat_event = NAT_CREATE;
++ nat_events_start++;
++ }
++
++ spin_lock_bh(&nat_lock);
++ list_add_tail(&nel->list, &nat_list);
++ spin_unlock_bh(&nat_lock);
++
++ return ret;
++}
++
++#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
++static struct notifier_block ctnl_notifier = {
++ .notifier_call = netflow_conntrack_event
++};
++#else
++static struct nf_ct_event_notifier ctnl_notifier = {
++ .fcn = netflow_conntrack_event
++};
++#endif /* since 2.6.31 */
++#endif /* CONFIG_NF_NAT_NEEDED */
++
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,23) && \
++ LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)
++static bool
++#else
++static int
++#endif
++#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
++netflow_target_check(const char *tablename, const void *entry, const struct xt_target *target,
++ void *targinfo,
++#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,18)
++ unsigned int targinfosize,
++#endif
++ unsigned int hook_mask)
++{
++#else
++netflow_target_check(const struct xt_tgchk_param *par)
++{
++ const char *tablename = par->table;
++ const struct xt_target *target = par->target;
++#endif
++ if (strcmp("nat", tablename) == 0) {
++ /* In the nat table we only see single packet per flow, which is useless. */
++ printk(KERN_ERR "%s target: is not valid in %s table\n", target->name, tablename);
++#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,35)
++#define CHECK_FAIL 0
++#define CHECK_OK 1
++#else
++#define CHECK_FAIL -EINVAL
++#define CHECK_OK 0
++#endif
++ return CHECK_FAIL;
++ }
++ if (target->family == AF_INET6 && protocol == 5) {
++ printk(KERN_ERR "ip6tables NETFLOW target is meaningful for protocol 9 or 10 only.\n");
++ return CHECK_FAIL;
++ }
++ return CHECK_OK;
++}
++
++#define SetXBit(x) (0x8000 >> (x)) /* Proper bit for htons later. */
++#ifndef IPPROTO_MH
++#define IPPROTO_MH 135
++#endif
++static inline __u16 observed_hdrs(const __u8 currenthdr)
++{
++ switch (currenthdr) {
++ case IPPROTO_TCP:
++ case IPPROTO_UDP:
++ /* For speed, in case switch is not optimized. */
++ return 0;
++ case IPPROTO_DSTOPTS: return SetXBit(0);
++ case IPPROTO_HOPOPTS: return SetXBit(1);
++ case IPPROTO_ROUTING: return SetXBit(5);
++ case IPPROTO_MH: return SetXBit(12);
++ case IPPROTO_ESP: return SetXBit(13);
++ case IPPROTO_AH: return SetXBit(14);
++ case IPPROTO_COMP: return SetXBit(15);
++ case IPPROTO_FRAGMENT: /* Handled elsewhere. */
++ /* Next is known headers. */
++ case IPPROTO_ICMPV6:
++ case IPPROTO_UDPLITE:
++ case IPPROTO_IPIP:
++ case IPPROTO_PIM:
++ case IPPROTO_GRE:
++ case IPPROTO_SCTP:
++#ifdef IPPROTO_L2TP
++ case IPPROTO_L2TP:
++#endif
++ case IPPROTO_DCCP:
++ return 0;
++ }
++ return SetXBit(3); /* Unknown header. */
++}
++
++/* http://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml */
++static const __u8 ip4_opt_table[] = {
++ [7] = 0, /* RR */ /* parsed manually becasue of 0 */
++ [134] = 1, /* CIPSO */
++ [133] = 2, /* E-SEC */
++ [68] = 3, /* TS */
++ [131] = 4, /* LSR */
++ [130] = 5, /* SEC */
++ [1] = 6, /* NOP */
++ [0] = 7, /* EOOL */
++ [15] = 8, /* ENCODE */
++ [142] = 9, /* VISA */
++ [205] = 10, /* FINN */
++ [12] = 11, /* MTUR */
++ [11] = 12, /* MTUP */
++ [10] = 13, /* ZSU */
++ [137] = 14, /* SSR */
++ [136] = 15, /* SID */
++ [151] = 16, /* DPS */
++ [150] = 17, /* NSAPA */
++ [149] = 18, /* SDB */
++ [147] = 19, /* ADDEXT */
++ [148] = 20, /* RTRALT */
++ [82] = 21, /* TR */
++ [145] = 22, /* EIP */
++ [144] = 23, /* IMITD */
++ [30] = 25, /* EXP */
++ [94] = 25, /* EXP */
++ [158] = 25, /* EXP */
++ [222] = 25, /* EXP */
++ [25] = 30, /* QS */
++ [152] = 31, /* UMP */
++};
++/* Parse IPv4 Options array int ipv4Options IPFIX value. */
++static inline __u32 ip4_options(const u_int8_t *p, const unsigned int optsize)
++{
++ __u32 ret = 0;
++ unsigned int i;
++
++ for (i = 0; likely(i < optsize); ) {
++ u_int8_t op = p[i++];
++
++ if (op == 7) /* RR: bit 0 */
++ ret |= 1;
++ else if (likely(op < ARRAY_SIZE(ip4_opt_table))) {
++ /* Btw, IANA doc is messed up in a crazy way:
++ * http://www.ietf.org/mail-archive/web/ipfix/current/msg06008.html (2011)
++ * I decided to follow IANA _text_ description from
++ * http://www.iana.org/assignments/ipfix/ipfix.xhtml (2013-09-18)
++ *
++ * Set proper bit for htonl later. */
++ if (ip4_opt_table[op])
++ ret |= 1 << (32 - ip4_opt_table[op]);
++ }
++ if (likely(i >= optsize || op == 0))
++ break;
++ else if (unlikely(op == 1))
++ continue;
++ else if (unlikely(p[i] < 2))
++ break;
++ else
++ i += p[i] - 1;
++ }
++ return ret;
++}
++
++#define TCPHDR_MAXSIZE (4 * 15)
++/* List of options: http://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml */
++static inline __u32 tcp_options(const struct sk_buff *skb, const unsigned int ptr, const struct tcphdr *th)
++{
++ const unsigned int optsize = th->doff * 4 - sizeof(struct tcphdr);
++ __u8 _opt[TCPHDR_MAXSIZE];
++ const u_int8_t *p;
++ __u32 ret;
++ unsigned int i;
++
++ p = skb_header_pointer(skb, ptr + sizeof(struct tcphdr), optsize, _opt);
++ if (unlikely(!p))
++ return 0;
++ ret = 0;
++ for (i = 0; likely(i < optsize); ) {
++ u_int8_t opt = p[i++];
++
++ if (likely(opt < 32)) {
++ /* IANA doc is messed up, see above. */
++ ret |= 1 << (32 - opt);
++ }
++ if (likely(i >= optsize || opt == 0))
++ break;
++ else if (unlikely(opt == 1))
++ continue;
++ else if (unlikely(p[i] < 2)) /* "silly options" */
++ break;
++ else
++ i += p[i] - 1;
++ }
++ return ret;
++}
+ /* packet receiver */
+ static unsigned int netflow_target(
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+@@ -1192,27 +2415,38 @@ static unsigned int netflow_target(
+ )
+ {
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24)
+- struct sk_buff *skb = *pskb;
++ const struct sk_buff *skb = *pskb;
++#endif
++ union {
++ struct iphdr ip;
++ struct ipv6hdr ip6;
++ } _iph, *iph;
++ unsigned int hash;
++#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
++ const int family = target->family;
++#else
++ const int family = par->family;
+ #endif
+- struct iphdr _iph, *iph;
+ struct ipt_netflow_tuple tuple;
+ struct ipt_netflow *nf;
+ __u8 tcp_flags;
+ struct netflow_aggr_n *aggr_n;
+ struct netflow_aggr_p *aggr_p;
+ __u8 s_mask, d_mask;
+- unsigned int hash;
+-
+- iph = skb_header_pointer(skb, 0, sizeof(_iph), &_iph); //iph = ip_hdr(skb);
+-
+- if (iph == NULL) {
++ unsigned int ptr;
++ int fragment;
++ size_t pkt_len;
++ int options = 0;
++ int tcpoptions = 0;
++
++ iph = skb_header_pointer(skb, 0, (likely(family == AF_INET))? sizeof(_iph.ip) : sizeof(_iph.ip6), &iph);
++ if (unlikely(iph == NULL)) {
+ NETFLOW_STAT_INC(truncated);
+ NETFLOW_STAT_INC(pkt_drop);
+ return IPT_CONTINUE;
+ }
+
+- tuple.s_addr = iph->saddr;
+- tuple.d_addr = iph->daddr;
++ tuple.l3proto = family;
+ tuple.s_port = 0;
+ tuple.d_port = 0;
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28)
+@@ -1220,30 +2454,118 @@ static unsigned int netflow_target(
+ #else
+ tuple.i_ifc = par->in? par->in->ifindex : -1;
+ #endif
+- tuple.protocol = iph->protocol;
+- tuple.tos = iph->tos;
+ tcp_flags = 0; /* Cisco sometimes have TCP ACK for non TCP packets, don't get it */
+ s_mask = 0;
+ d_mask = 0;
+
+- if (iph->frag_off & htons(IP_OFFSET))
++ if (likely(family == AF_INET)) {
++ tuple.src = (union nf_inet_addr){ .ip = iph->ip.saddr };
++ tuple.dst = (union nf_inet_addr){ .ip = iph->ip.daddr };
++ tuple.tos = iph->ip.tos;
++ tuple.protocol = iph->ip.protocol;
++ fragment = unlikely(iph->ip.frag_off & htons(IP_OFFSET));
++ ptr = iph->ip.ihl * 4;
++ pkt_len = ntohs(iph->ip.tot_len);
++
++#define IPHDR_MAXSIZE (4 * 15)
++ if (unlikely(iph->ip.ihl * 4 > sizeof(struct iphdr))) {
++ u_int8_t _opt[IPHDR_MAXSIZE - sizeof(struct iphdr)];
++ const u_int8_t *op;
++ unsigned int optsize = iph->ip.ihl * 4 - sizeof(struct iphdr);
++
++ op = skb_header_pointer(skb, sizeof(struct iphdr), optsize, _opt);
++ if (likely(op))
++ options = ip4_options(op, optsize);
++ }
++ } else {
++ __u8 currenthdr;
++
++ tuple.src.in6 = iph->ip6.saddr;
++ tuple.dst.in6 = iph->ip6.daddr;
++ tuple.tos = iph->ip6.priority;
++ fragment = 0;
++ ptr = sizeof(struct ipv6hdr);
++ pkt_len = ntohs(iph->ip6.payload_len) + sizeof(struct ipv6hdr);
++
++ currenthdr = iph->ip6.nexthdr;
++ while (currenthdr != NEXTHDR_NONE && ipv6_ext_hdr(currenthdr)) {
++ struct ipv6_opt_hdr _hdr;
++ const struct ipv6_opt_hdr *hp;
++ unsigned int hdrlen = 0;
++
++ options |= observed_hdrs(currenthdr);
++ hp = skb_header_pointer(skb, ptr, sizeof(_hdr), &_hdr);
++ if (hp == NULL) {
++ /* We have src/dst, so must account something. */
++ tuple.protocol = currenthdr;
++ fragment = 3;
++ goto do_protocols;
++ }
++
++ switch (currenthdr) {
++ case IPPROTO_FRAGMENT: {
++ struct frag_hdr _fhdr;
++ const struct frag_hdr *fh;
++
++ fh = skb_header_pointer(skb, ptr, sizeof(_fhdr),
++ &_fhdr);
++ if (fh == NULL) {
++ tuple.protocol = currenthdr;
++ fragment = 2;
++ goto do_protocols;
++ }
++ fragment = 1;
++#define FRA0 SetXBit(4) /* Fragment header - first fragment */
++#define FRA1 SetXBit(6) /* Fragmentation header - not first fragment */
++ options |= (ntohs(fh->frag_off) & 0xFFF8)? FRA1 : FRA0;
++ hdrlen = 8;
++ break;
++ }
++ case IPPROTO_AH: {
++ struct ip_auth_hdr _hdr, *hp;
++
++ if (likely(hp = skb_header_pointer(skb, ptr, 8, &_hdr))) {
++ tuple.s_port = hp->spi >> 16;
++ tuple.d_port = hp->spi;
++ }
++ hdrlen = (hp->hdrlen + 2) << 2;
++ break;
++ }
++ default:
++ hdrlen = ipv6_optlen(hp);
++ }
++ currenthdr = hp->nexthdr;
++ ptr += hdrlen;
++ }
++ tuple.protocol = currenthdr;
++ options |= observed_hdrs(currenthdr);
++ }
++
++do_protocols:
++ if (fragment) {
++ /* if conntrack is enabled it should defrag on pre-routing and local-out */
+ NETFLOW_STAT_INC(frags);
+- else {
++ } else {
+ switch (tuple.protocol) {
+ case IPPROTO_TCP: {
+ struct tcphdr _hdr, *hp;
+
+- if ((hp = skb_header_pointer(skb, iph->ihl * 4, 14, &_hdr))) {
++ if (likely(hp = skb_header_pointer(skb, ptr, 14, &_hdr))) {
+ tuple.s_port = hp->source;
+ tuple.d_port = hp->dest;
+ tcp_flags = (u_int8_t)(ntohl(tcp_flag_word(hp)) >> 16);
++
++ if (unlikely(hp->doff * 4 > sizeof(struct tcphdr)))
++ tcpoptions = tcp_options(skb, ptr, hp);
+ }
+ break;
+ }
+- case IPPROTO_UDP: {
++ case IPPROTO_UDP:
++ case IPPROTO_UDPLITE:
++ case IPPROTO_SCTP: {
+ struct udphdr _hdr, *hp;
+
+- if ((hp = skb_header_pointer(skb, iph->ihl * 4, 4, &_hdr))) {
++ if (likely(hp = skb_header_pointer(skb, ptr, 4, &_hdr))) {
+ tuple.s_port = hp->source;
+ tuple.d_port = hp->dest;
+ }
+@@ -1252,72 +2574,111 @@ static unsigned int netflow_target(
+ case IPPROTO_ICMP: {
+ struct icmphdr _hdr, *hp;
+
+- if ((hp = skb_header_pointer(skb, iph->ihl * 4, 2, &_hdr)))
+- tuple.d_port = (hp->type << 8) | hp->code;
++ if (likely(family == AF_INET &&
++ (hp = skb_header_pointer(skb, ptr, 2, &_hdr))))
++ tuple.d_port = htons((hp->type << 8) | hp->code);
+ break;
+ }
++ case IPPROTO_ICMPV6: {
++ struct icmp6hdr _icmp6h, *ic;
++
++ if (likely(family == AF_INET6 &&
++ (ic = skb_header_pointer(skb, ptr, 2, &_icmp6h))))
++ tuple.d_port = htons((ic->icmp6_type << 8) | ic->icmp6_code);
++ break;
++ }
+ case IPPROTO_IGMP: {
+- struct igmphdr *_hdr, *hp;
++ struct igmphdr _hdr, *hp;
+
+- if ((hp = skb_header_pointer(skb, iph->ihl * 4, 1, &_hdr)))
++ if (likely(hp = skb_header_pointer(skb, ptr, 1, &_hdr)))
+ tuple.d_port = hp->type;
+ }
+ break;
++ case IPPROTO_AH: { /* IPSEC */
++ struct ip_auth_hdr _hdr, *hp;
++
++ if (likely(family == AF_INET && /* For IPv6 it's parsed above. */
++ (hp = skb_header_pointer(skb, ptr, 8, &_hdr)))) {
++ tuple.s_port = hp->spi >> 16;
++ tuple.d_port = hp->spi;
++ }
++ break;
++ }
++ case IPPROTO_ESP: {
++ struct ip_esp_hdr _hdr, *hp;
++
++ if (likely(hp = skb_header_pointer(skb, ptr, 4, &_hdr)))
++ tuple.s_port = hp->spi >> 16;
++ tuple.d_port = hp->spi;
++ }
++ break;
+ }
+ } /* not fragmented */
+
+ /* aggregate networks */
+ read_lock_bh(&aggr_lock);
+- list_for_each_entry(aggr_n, &aggr_n_list, list)
+- if ((ntohl(tuple.s_addr) & aggr_n->mask) == aggr_n->addr) {
+- tuple.s_addr &= htonl(aggr_n->aggr_mask);
+- s_mask = aggr_n->prefix;
+- break;
+- }
+- list_for_each_entry(aggr_n, &aggr_n_list, list)
+- if ((ntohl(tuple.d_addr) & aggr_n->mask) == aggr_n->addr) {
+- tuple.d_addr &= htonl(aggr_n->aggr_mask);
+- d_mask = aggr_n->prefix;
+- break;
+- }
++ if (family == AF_INET) {
++ list_for_each_entry(aggr_n, &aggr_n_list, list)
++ if (unlikely((ntohl(tuple.src.ip) & aggr_n->mask) == aggr_n->addr)) {
++ tuple.src.ip &= htonl(aggr_n->aggr_mask);
++ s_mask = aggr_n->prefix;
++ atomic_inc(&aggr_n->usage);
++ break;
++ }
++ list_for_each_entry(aggr_n, &aggr_n_list, list)
++ if (unlikely((ntohl(tuple.dst.ip) & aggr_n->mask) == aggr_n->addr)) {
++ tuple.dst.ip &= htonl(aggr_n->aggr_mask);
++ d_mask = aggr_n->prefix;
++ atomic_inc(&aggr_n->usage);
++ break;
++ }
++ }
+
+- /* aggregate ports */
+- list_for_each_entry(aggr_p, &aggr_p_list, list)
+- if (ntohs(tuple.s_port) >= aggr_p->port1 &&
+- ntohs(tuple.s_port) <= aggr_p->port2) {
+- tuple.s_port = htons(aggr_p->aggr_port);
+- break;
+- }
++ if (tuple.protocol == IPPROTO_TCP ||
++ tuple.protocol == IPPROTO_UDP ||
++ tuple.protocol == IPPROTO_SCTP ||
++ tuple.protocol == IPPROTO_UDPLITE) {
++ /* aggregate ports */
++ list_for_each_entry(aggr_p, &aggr_p_list, list)
++ if (unlikely(ntohs(tuple.s_port) >= aggr_p->port1 &&
++ ntohs(tuple.s_port) <= aggr_p->port2)) {
++ tuple.s_port = htons(aggr_p->aggr_port);
++ atomic_inc(&aggr_p->usage);
++ break;
++ }
+
+- list_for_each_entry(aggr_p, &aggr_p_list, list)
+- if (ntohs(tuple.d_port) >= aggr_p->port1 &&
+- ntohs(tuple.d_port) <= aggr_p->port2) {
+- tuple.d_port = htons(aggr_p->aggr_port);
+- break;
+- }
++ list_for_each_entry(aggr_p, &aggr_p_list, list)
++ if (unlikely(ntohs(tuple.d_port) >= aggr_p->port1 &&
++ ntohs(tuple.d_port) <= aggr_p->port2)) {
++ tuple.d_port = htons(aggr_p->aggr_port);
++ atomic_inc(&aggr_p->usage);
++ break;
++ }
++ }
+ read_unlock_bh(&aggr_lock);
+
+ hash = hash_netflow(&tuple);
+- spin_lock_bh(&ipt_netflow_lock);
++ read_lock_bh(&htable_rwlock);
++ spin_lock(&htable_locks[hash & LOCK_COUNT_MASK]);
+ /* record */
+ nf = ipt_netflow_find(&tuple, hash);
+- if (!nf) {
+- if (maxflows > 0 && atomic_read(&ipt_netflow_count) >= maxflows) {
++ if (unlikely(!nf)) {
++ struct rtable *rt;
++
++ if (unlikely(maxflows > 0 && atomic_read(&ipt_netflow_count) >= maxflows)) {
+ /* This is DOS attack prevention */
+ NETFLOW_STAT_INC(maxflows_err);
+ NETFLOW_STAT_INC(pkt_drop);
+- NETFLOW_STAT_ADD(traf_drop, ntohs(iph->tot_len));
+- spin_unlock_bh(&ipt_netflow_lock);
+- return IPT_CONTINUE;
++ NETFLOW_STAT_ADD(traf_drop, pkt_len);
++ goto unlock_return;
+ }
+
+ nf = init_netflow(&tuple, skb, hash);
+- if (!nf || IS_ERR(nf)) {
++ if (unlikely(!nf || IS_ERR(nf))) {
+ NETFLOW_STAT_INC(alloc_err);
+ NETFLOW_STAT_INC(pkt_drop);
+- NETFLOW_STAT_ADD(traf_drop, ntohs(iph->tot_len));
+- spin_unlock_bh(&ipt_netflow_lock);
+- return IPT_CONTINUE;
++ NETFLOW_STAT_ADD(traf_drop, pkt_len);
++ goto unlock_return;
+ }
+
+ nf->ts_first = jiffies;
+@@ -1330,31 +2691,68 @@ static unsigned int netflow_target(
+ nf->s_mask = s_mask;
+ nf->d_mask = d_mask;
+
+- if (debug > 2)
+- printk(KERN_INFO "ipt_netflow: new (%u) %hd:%hd SRC=%u.%u.%u.%u:%u DST=%u.%u.%u.%u:%u\n",
++#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,26)
++ rt = (struct rtable *)skb->dst;
++#else /* since 2.6.26 */
++#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
++ rt = skb->rtable;
++#else /* since 2.6.31 */
++ rt = skb_rtable(skb);
++#endif
++#endif
++ if (likely(family == AF_INET)) {
++ if (rt)
++ nf->nh.ip = rt->rt_gateway;
++ } else {
++ if (rt)
++ nf->nh.in6 = ((struct rt6_info *)rt)->rt6i_gateway;
++ nf->flow_label = (iph->ip6.flow_lbl[0] << 16) |
++ (iph->ip6.flow_lbl[1] << 8) | (iph->ip6.flow_lbl[2]);
++ }
++#if 0
++ if (unlikely(debug > 2))
++ printk(KERN_INFO "ipt_NETFLOW: new (%u) %hd:%hd SRC=%u.%u.%u.%u:%u DST=%u.%u.%u.%u:%u\n",
+ atomic_read(&ipt_netflow_count),
+ tuple.i_ifc, nf->o_ifc,
+ NIPQUAD(tuple.s_addr), ntohs(tuple.s_port),
+ NIPQUAD(tuple.d_addr), ntohs(tuple.d_port));
++#endif
+ } else {
+ /* ipt_netflow_list is sorted by access time:
+ * most recently accessed flows are at head, old flows remain at tail
+ * this function bubble up flow to the head */
++ spin_lock(&hlist_lock);
+ list_move(&nf->list, &ipt_netflow_list);
++ spin_unlock(&hlist_lock);
+ }
+
++#ifdef CONFIG_NF_CONNTRACK_MARK
++ {
++ struct nf_conn *ct;
++ enum ip_conntrack_info ctinfo;
++ ct = nf_ct_get(skb, &ctinfo);
++ if (ct)
++ nf->mark = ct->mark;
++ }
++#endif
++
+ nf->nr_packets++;
+- nf->nr_bytes += ntohs(iph->tot_len);
++ nf->nr_bytes += pkt_len;
+ nf->ts_last = jiffies;
+ nf->tcp_flags |= tcp_flags;
++ nf->options |= options;
++ if (tuple.protocol == IPPROTO_TCP)
++ nf->tcpoptions |= tcpoptions;
+
+ NETFLOW_STAT_INC(pkt_total);
+- NETFLOW_STAT_ADD(traf_total, ntohs(iph->tot_len));
++ NETFLOW_STAT_ADD(traf_total, pkt_len);
+
+- if (active_needs_export(nf, active_timeout * HZ)) {
++ if (likely(active_needs_export(nf, active_timeout * HZ))) {
+ /* ok, if this active flow to be exported
+ * bubble it to the tail */
++ spin_lock(&hlist_lock);
+ list_move_tail(&nf->list, &ipt_netflow_list);
++ spin_unlock(&hlist_lock);
+
+ /* Blog: I thought about forcing timer to wake up sooner if we have
+ * enough exportable flows, but in fact this doesn't have much sense,
+@@ -1363,35 +2761,194 @@ static unsigned int netflow_target(
+ * limited size). But yes, this is disputable. */
+ }
+
+- spin_unlock_bh(&ipt_netflow_lock);
++unlock_return:
++ spin_unlock(&htable_locks[hash & LOCK_COUNT_MASK]);
++ read_unlock_bh(&htable_rwlock);
+
+ return IPT_CONTINUE;
+ }
+
+-static struct ipt_target ipt_netflow_reg = {
+- .name = "NETFLOW",
+- .target = netflow_target,
+- .family = AF_INET,
+-#ifndef RAW_PROMISC_HACK
+- .table = "filter",
+-#ifndef NF_IP_LOCAL_IN /* 2.6.25 */
+- .hooks = (1 << NF_INET_LOCAL_IN) | (1 << NF_INET_FORWARD) |
+- (1 << NF_INET_LOCAL_OUT),
+-#else
+- .hooks = (1 << NF_IP_LOCAL_IN) | (1 << NF_IP_FORWARD) |
+- (1 << NF_IP_LOCAL_OUT),
+-#endif /* NF_IP_LOCAL_IN */
++#ifdef CONFIG_NF_NAT_NEEDED
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
++ /* Below 2.6.31 we don't need to handle callback chain manually. */
++
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
++#define NET_STRUCT struct net *net
++#define NET_ARG net,
++#define nf_conntrack_event_cb net->ct.nf_conntrack_event_cb
+ #else
+- .table = "raw",
+-#ifndef NF_IP_LOCAL_IN
+- .hooks = (1 << NF_INET_LOCAL_IN) | (1 << NF_INET_FORWARD) |
+- (1 << NF_INET_LOCAL_OUT) | (1 << NF_INET_PRE_ROUTING),
++#define NET_STRUCT void
++#define NET_ARG
++#endif
++static int set_notifier_cb(NET_STRUCT)
++{
++ struct nf_ct_event_notifier *notifier;
++
++ notifier = rcu_dereference(nf_conntrack_event_cb);
++ if (notifier == NULL) {
++ /* Polite mode. */
++ nf_conntrack_register_notifier(NET_ARG &ctnl_notifier);
++ } else if (notifier != &ctnl_notifier) {
++ if (!saved_event_cb)
++ saved_event_cb = notifier;
++ else if (saved_event_cb != notifier)
++ printk(KERN_ERR "natevents_net_init: %p != %p (report error.)\n",
++ saved_event_cb, notifier);
++ rcu_assign_pointer(nf_conntrack_event_cb, &ctnl_notifier);
++ } else
++ printk(KERN_ERR "ipt_NETFLOW: natevents already enabled.\n");
++ return 0;
++}
++static void unset_notifier_cb(NET_STRUCT)
++{
++ struct nf_ct_event_notifier *notifier;
++
++ notifier = rcu_dereference(nf_conntrack_event_cb);
++ if (notifier == &ctnl_notifier) {
++ if (saved_event_cb == NULL)
++ nf_conntrack_unregister_notifier(NET_ARG &ctnl_notifier);
++ else
++ rcu_assign_pointer(nf_conntrack_event_cb, saved_event_cb);
++ } else
++ printk(KERN_ERR "ipt_NETFLOW: natevents already disabled.\n");
++}
++
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
++#undef nf_conntrack_event_cb
++static struct pernet_operations natevents_net_ops = {
++ .init = set_notifier_cb,
++ .exit = unset_notifier_cb
++};
++#endif
++#endif /* since 2.6.31 */
++
++static DEFINE_MUTEX(events_lock);
++/* Both functions may be called multiple times. */
++static void register_ct_events(void)
++{
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
++#define NETLINK_M "nf_conntrack_netlink"
++ struct module *netlink_m;
++ static int referenced = 0;
++#endif
++
++ printk(KERN_INFO "ipt_NETFLOW: enable natevents.\n");
++ mutex_lock(&events_lock);
++
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
++ /* Pre-load netlink module who will be first notifier
++ * user, and then hijack nf_conntrack_event_cb from it. */
++ if (
++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,2,0)
++ !rcu_dereference(nf_conntrack_event_cb) ||
++#endif
++ !(netlink_m = find_module(NETLINK_M))) {
++ printk("Loading " NETLINK_M "\n");
++ request_module(NETLINK_M);
++ }
++ /* Reference netlink module to prevent it's unsafe unload before us. */
++ if (!referenced && (netlink_m = find_module(NETLINK_M))) {
++ referenced++;
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,35)
++#define use_module ref_module
++#endif
++ use_module(THIS_MODULE, netlink_m);
++ }
++
++ /* Register ct events callback. */
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
++ register_pernet_subsys(&natevents_net_ops);
+ #else
+- .hooks = (1 << NF_IP_LOCAL_IN) | (1 << NF_IP_FORWARD) |
+- (1 << NF_IP_LOCAL_OUT) | (1 << NF_IP_PRE_ROUTING),
+-#endif /* NF_IP_LOCAL_IN */
+-#endif /* !RAW_PROMISC_HACK */
+- .me = THIS_MODULE
++ set_notifier_cb();
++#endif
++#else /* below v2.6.31 */
++ if (!natevents && nf_conntrack_register_notifier(&ctnl_notifier) < 0)
++ printk(KERN_ERR "Can't register conntrack notifier, natevents disabled.\n");
++ else
++#endif
++ natevents = 1;
++ mutex_unlock(&events_lock);
++}
++
++static void unregister_ct_events(void)
++{
++ printk(KERN_INFO "ipt_NETFLOW: disable natevents.\n");
++ mutex_lock(&events_lock);
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,31)
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,2,0)
++ unregister_pernet_subsys(&natevents_net_ops);
++#else /* < v3.2 */
++ unset_notifier_cb();
++#endif /* v3.2 */
++ rcu_assign_pointer(saved_event_cb, NULL);
++#else /* < v2.6.31 */
++ nf_conntrack_unregister_notifier(&ctnl_notifier);
++#endif
++ natevents = 0;
++ mutex_unlock(&events_lock);
++}
++#endif /* CONFIG_NF_NAT_NEEDED */
++
++#ifndef NF_IP_LOCAL_IN /* 2.6.25 */
++#define NF_IP_PRE_ROUTING NF_INET_PRE_ROUTING
++#define NF_IP_LOCAL_IN NF_INET_LOCAL_IN
++#define NF_IP_FORWARD NF_INET_FORWARD
++#define NF_IP_LOCAL_OUT NF_INET_LOCAL_OUT
++#define NF_IP_POST_ROUTING NF_INET_POST_ROUTING
++#endif
++
++#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
++/* net/netfilter/x_tables.c */
++static void xt_unregister_targets(struct xt_target *target, unsigned int n)
++{
++ unsigned int i;
++
++ for (i = 0; i < n; i++)
++ xt_unregister_target(&target[i]);
++}
++static int xt_register_targets(struct xt_target *target, unsigned int n)
++{
++ unsigned int i;
++
++ int err = 0;
++ for (i = 0; i < n; i++)
++ if ((err = xt_register_target(&target[i])))
++ goto err;
++ return err;
++err:
++ if (i > 0)
++ xt_unregister_targets(target, i);
++ return err;
++}
++#endif
++
++static struct ipt_target ipt_netflow_reg[] __read_mostly = {
++ {
++ .name = "NETFLOW",
++ .target = netflow_target,
++ .checkentry = netflow_target_check,
++ .family = AF_INET,
++ .hooks =
++ (1 << NF_IP_PRE_ROUTING) |
++ (1 << NF_IP_LOCAL_IN) |
++ (1 << NF_IP_FORWARD) |
++ (1 << NF_IP_LOCAL_OUT) |
++ (1 << NF_IP_POST_ROUTING),
++ .me = THIS_MODULE
++ },
++ {
++ .name = "NETFLOW",
++ .target = netflow_target,
++ .checkentry = netflow_target_check,
++ .family = AF_INET6,
++ .hooks =
++ (1 << NF_IP_PRE_ROUTING) |
++ (1 << NF_IP_LOCAL_IN) |
++ (1 << NF_IP_FORWARD) |
++ (1 << NF_IP_LOCAL_OUT) |
++ (1 << NF_IP_POST_ROUTING),
++ .me = THIS_MODULE
++ },
+ };
+
+ static int __init ipt_netflow_init(void)
+@@ -1399,11 +2956,16 @@ static int __init ipt_netflow_init(void)
+ #ifdef CONFIG_PROC_FS
+ struct proc_dir_entry *proc_stat;
+ #endif
++ printk(KERN_INFO "ipt_NETFLOW version %s, srcversion %s\n",
++ IPT_NETFLOW_VERSION, THIS_MODULE->srcversion);
+
+ get_random_bytes(&ipt_netflow_hash_rnd, 4);
+
+ /* determine hash size (idea from nf_conntrack_core.c) */
+ if (!hashsize) {
++#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,11,0)
++#define num_physpages totalram_pages
++#endif
+ hashsize = (((num_physpages << PAGE_SHIFT) / 16384)
+ / sizeof(struct hlist_head));
+ if (num_physpages > (1024 * 1024 * 1024 / PAGE_SIZE))
+@@ -1411,8 +2973,7 @@ static int __init ipt_netflow_init(void)
+ }
+ if (hashsize < 16)
+ hashsize = 16;
+- printk(KERN_INFO "ipt_netflow version %s (%u buckets)\n",
+- IPT_NETFLOW_VERSION, hashsize);
++ printk(KERN_INFO "ipt_NETFLOW: hashsize %u\n", hashsize);
+
+ ipt_netflow_hash_size = hashsize;
+ ipt_netflow_hash = alloc_hashtable(ipt_netflow_hash_size);
+@@ -1434,12 +2995,18 @@ static int __init ipt_netflow_init(void)
+ }
+
+ #ifdef CONFIG_PROC_FS
++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0)
+ proc_stat = create_proc_entry("ipt_netflow", S_IRUGO, INIT_NET(proc_net_stat));
++#else
++ proc_stat = proc_create("ipt_netflow", S_IRUGO, INIT_NET(proc_net_stat), &nf_seq_fops);
++#endif
+ if (!proc_stat) {
+ printk(KERN_ERR "Unable to create /proc/net/stat/ipt_netflow entry\n");
+ goto err_free_netflow_slab;
+ }
++#if LINUX_VERSION_CODE < KERNEL_VERSION(3,10,0)
+ proc_stat->proc_fops = &nf_seq_fops;
++#endif
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30)
+ proc_stat->owner = THIS_MODULE;
+ #endif
+@@ -1480,21 +3047,28 @@ static int __init ipt_netflow_init(void)
+ }
+ add_aggregation(aggregation);
+
+- __start_scan_worker();
++ netflow_switch_version(protocol);
++ _schedule_scan_worker(0);
+ setup_timer(&rate_timer, rate_timer_calc, 0);
+ mod_timer(&rate_timer, jiffies + (HZ * SAMPLERATE));
+
+- if (xt_register_target(&ipt_netflow_reg))
++ peakflows_at = jiffies;
++ if (xt_register_targets(ipt_netflow_reg, ARRAY_SIZE(ipt_netflow_reg)))
+ goto err_stop_timer;
+
+- peakflows_at = jiffies;
++#ifdef CONFIG_NF_NAT_NEEDED
++ if (natevents)
++ register_ct_events();
++#endif
+
+- printk(KERN_INFO "ipt_netflow loaded.\n");
++ printk(KERN_INFO "ipt_NETFLOW is loaded.\n");
+ return 0;
+
+ err_stop_timer:
+- __stop_scan_worker();
++ _unschedule_scan_worker();
++ netflow_scan_and_export(AND_FLUSH);
+ del_timer_sync(&rate_timer);
++ free_templates();
+ destination_removeall();
+ aggregation_remove(&aggr_n_list);
+ aggregation_remove(&aggr_p_list);
+@@ -1506,17 +3080,18 @@ err_free_proc_stat:
+ #ifdef CONFIG_PROC_FS
+ remove_proc_entry("ipt_netflow", INIT_NET(proc_net_stat));
+ err_free_netflow_slab:
+-#endif
++#endif
+ kmem_cache_destroy(ipt_netflow_cachep);
+ err_free_hash:
+ vfree(ipt_netflow_hash);
+ err:
++ printk(KERN_INFO "ipt_NETFLOW is not loaded.\n");
+ return -ENOMEM;
+ }
+
+ static void __exit ipt_netflow_fini(void)
+ {
+- printk(KERN_INFO "ipt_netflow unloading..\n");
++ printk(KERN_INFO "ipt_NETFLOW unloading..\n");
+
+ #ifdef CONFIG_SYSCTL
+ unregister_sysctl_table(netflow_sysctl_header);
+@@ -1524,14 +3099,18 @@ static void __exit ipt_netflow_fini(void)
+ #ifdef CONFIG_PROC_FS
+ remove_proc_entry("ipt_netflow", INIT_NET(proc_net_stat));
+ #endif
+-
+- xt_unregister_target(&ipt_netflow_reg);
+- __stop_scan_worker();
+- netflow_scan_and_export(1);
++ xt_unregister_targets(ipt_netflow_reg, ARRAY_SIZE(ipt_netflow_reg));
++#ifdef CONFIG_NF_NAT_NEEDED
++ if (natevents)
++ unregister_ct_events();
++#endif
++ _unschedule_scan_worker();
++ netflow_scan_and_export(AND_FLUSH);
+ del_timer_sync(&rate_timer);
+
+ synchronize_sched();
+
++ free_templates();
+ destination_removeall();
+ aggregation_remove(&aggr_n_list);
+ aggregation_remove(&aggr_p_list);
+@@ -1539,7 +3118,7 @@ static void __exit ipt_netflow_fini(void)
+ kmem_cache_destroy(ipt_netflow_cachep);
+ vfree(ipt_netflow_hash);
+
+- printk(KERN_INFO "ipt_netflow unloaded.\n");
++ printk(KERN_INFO "ipt_NETFLOW unloaded.\n");
+ }
+
+ module_init(ipt_netflow_init);
+diff --git a/ipt_NETFLOW.h b/ipt_NETFLOW.h
+index 4a7b645..749f985 100644
+--- a/ipt_NETFLOW.h
++++ b/ipt_NETFLOW.h
+@@ -35,8 +35,8 @@ struct netflow5_record {
+ __be16 o_ifc;
+ __be32 nr_packets;
+ __be32 nr_octets;
+- __be32 ts_first;
+- __be32 ts_last;
++ __be32 first_ms;
++ __be32 last_ms;
+ __be16 s_port;
+ __be16 d_port;
+ __u8 reserved;
+@@ -54,9 +54,9 @@ struct netflow5_record {
+ struct netflow5_pdu {
+ __be16 version;
+ __be16 nr_records;
+- __be32 ts_uptime;
+- __be32 ts_usecs;
+- __be32 ts_unsecs;
++ __be32 ts_uptime; /* ms */
++ __be32 ts_usecs; /* s */
++ __be32 ts_unsecs; /* ns */
+ __be32 seq;
+ __u8 eng_type;
+ __u8 eng_id;
+@@ -65,42 +65,185 @@ struct netflow5_pdu {
+ } __attribute__ ((packed));
+ #define NETFLOW5_HEADER_SIZE (sizeof(struct netflow5_pdu) - NETFLOW5_RECORDS_MAX * sizeof(struct netflow5_record))
+
++/* NetFlow v9 RFC http://www.ietf.org/rfc/rfc3954.txt */
++enum {
++ IN_BYTES = 1,
++ IN_PKTS = 2,
++ PROTOCOL = 4,
++ TOS = 5,
++ TCP_FLAGS = 6,
++ L4_SRC_PORT = 7,
++ IPV4_SRC_ADDR = 8,
++ SRC_MASK = 9,
++ INPUT_SNMP = 10,
++ L4_DST_PORT = 11,
++ IPV4_DST_ADDR = 12,
++ DST_MASK = 13,
++ OUTPUT_SNMP = 14,
++ IPV4_NEXT_HOP = 15,
++ //SRC_AS = 16,
++ //DST_AS = 17,
++ //BGP_IPV4_NEXT_HOP = 18,
++ //MUL_DST_PKTS = 19,
++ //MUL_DST_BYTES = 20,
++ LAST_SWITCHED = 21,
++ FIRST_SWITCHED = 22,
++ IPV6_SRC_ADDR = 27,
++ IPV6_DST_ADDR = 28,
++ IPV6_FLOW_LABEL = 31,
++ ICMP_TYPE = 32,
++ MUL_IGMP_TYPE = 33,
++ //TOTAL_BYTES_EXP = 40,
++ //TOTAL_PKTS_EXP = 41,
++ //TOTAL_FLOWS_EXP = 42,
++ IPV6_NEXT_HOP = 62,
++ IPV6_OPTION_HEADERS = 64,
++ commonPropertiesId = 137, /* for MARK */
++ ipv4Options = 208,
++ tcpOptions = 209,
++ postNATSourceIPv4Address = 225,
++ postNATDestinationIPv4Address = 226,
++ postNAPTSourceTransportPort = 227,
++ postNAPTDestinationTransportPort = 228,
++ natEvent = 230,
++ postNATSourceIPv6Address = 281,
++ postNATDestinationIPv6Address = 282,
++ IPSecSPI = 295,
++ observationTimeMilliseconds = 323,
++ observationTimeMicroseconds = 324,
++ observationTimeNanoseconds = 325,
++};
++
++enum {
++ FLOWSET_TEMPLATE = 0,
++ FLOWSET_OPTIONS = 1,
++ IPFIX_TEMPLATE = 2,
++ IPFIX_OPTIONS = 3,
++ FLOWSET_DATA_FIRST = 256,
++};
++
++struct flowset_template {
++ __be16 flowset_id;
++ __be16 length;
++ __be16 template_id;
++ __be16 field_count;
++} __attribute__ ((packed));
++
++struct flowset_data {
++ __be16 flowset_id;
++ __be16 length;
++} __attribute__ ((packed));
++
++/* NetFlow v9 packet. */
++struct netflow9_pdu {
++ __be16 version;
++ __be16 nr_records;
++ __be32 sys_uptime_ms;
++ __be32 export_time_s;
++ __be32 seq;
++ __be32 source_id; /* Exporter Observation Domain */
++ __u8 data[1400];
++} __attribute__ ((packed));
++
++/* IPFIX packet. */
++struct ipfix_pdu {
++ __be16 version;
++ __be16 length;
++ __be32 export_time_s;
++ __be32 seq;
++ __be32 odomain_id; /* Observation Domain ID */
++ __u8 data[1400];
++} __attribute__ ((packed));
++
++/* Maximum bytes flow can have, after it's reached flow will become
++ * not searchable and will be exported soon. */
++#define FLOW_FULL_WATERMARK 0xffefffff
++
++#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,25)
++union nf_inet_addr {
++ __be32 ip;
++ __be32 ip6[4];
++ struct in_addr in;
++ struct in6_addr in6;
++};
++#endif
++
+ /* hashed data which identify unique flow */
++/* 16+16 + 2+2 + 2+1+1+1 = 41 */
+ struct ipt_netflow_tuple {
+- __be32 s_addr; // Network byte order
+- __be32 d_addr; // -"-
+- __be16 s_port; // -"-
++ union nf_inet_addr src;
++ union nf_inet_addr dst;
++ __be16 s_port; // Network byte order
+ __be16 d_port; // -"-
+- __be16 i_ifc; // Local byte order
++ __u16 i_ifc; // Host byte order
+ __u8 protocol;
+ __u8 tos;
++ __u8 l3proto;
+ };
+-/* tuple size is rounded to u32s */
+-#define NETFLOW_TUPLE_SIZE (sizeof(struct ipt_netflow_tuple) / 4)
+-
+-/* maximum bytes flow can have, after it reached flow become not searchable and will be exported soon */
+-#define FLOW_FULL_WATERMARK 0xffefffff
+
+-/* flow entry */
++/* hlist[2] + tuple[]: 8+8 + 41 = 57 (less than usual cache line, 64) */
+ struct ipt_netflow {
+ struct hlist_node hlist; // hashtable search chain
+- struct list_head list; // all flows chain
+
+ /* unique per flow data (hashed, NETFLOW_TUPLE_SIZE) */
+ struct ipt_netflow_tuple tuple;
+
+ /* volatile data */
+- __be16 o_ifc;
++ union nf_inet_addr nh;
++ __u16 o_ifc;
+ __u8 s_mask;
+ __u8 d_mask;
++ __u8 tcp_flags; /* `OR' of all tcp flags */
+
+ /* flow statistics */
+ u_int32_t nr_packets;
+ u_int32_t nr_bytes;
+- unsigned long ts_first;
+- unsigned long ts_last;
+- __u8 tcp_flags; /* `OR' of all tcp flags */
++ union {
++ struct {
++ unsigned long first;
++ unsigned long last;
++ } ts;
++ ktime_t ts_obs;
++ } _ts_un;
++#define ts_first _ts_un.ts.first
++#define ts_last _ts_un.ts.last
++#define ts_obs _ts_un.ts_obs
++ u_int32_t flow_label; /* IPv6 */
++ u_int32_t options; /* IPv4(16) & IPv6(32) Options */
++ u_int32_t tcpoptions;
++#ifdef CONFIG_NF_CONNTRACK_MARK
++ u_int32_t mark; /* Exported as commonPropertiesId */
++#endif
++#ifdef CONFIG_NF_NAT_NEEDED
++ __be32 s_as;
++ __be32 d_as;
++ struct nat_event *nat;
++#endif
++ struct list_head list; // all flows chain
++ spinlock_t *lock;
++};
++
++#ifdef CONFIG_NF_NAT_NEEDED
++enum {
++ NAT_CREATE, NAT_DESTROY, NAT_POOLEXHAUSTED
+ };
++struct nat_event {
++ struct list_head list;
++ struct {
++ __be32 s_addr;
++ __be32 d_addr;
++ __be16 s_port;
++ __be16 d_port;
++ } pre, post;
++ ktime_t ts_ktime;
++ unsigned long ts_jiffies;
++ __u8 protocol;
++ __u8 nat_event;
++};
++#define IS_DUMMY_FLOW(nf) (nf->nat)
++#else
++#define IS_DUMMY_FLOW(nf) 0
++#endif
+
+ static inline int ipt_netflow_tuple_equal(const struct ipt_netflow_tuple *t1,
+ const struct ipt_netflow_tuple *t2)
+@@ -115,11 +258,13 @@ struct ipt_netflow_sock {
+ unsigned short port;
+ atomic_t wmem_peak; // sk_wmem_alloc peak value
+ atomic_t err_full; // socket filled error
++ atomic_t err_connect; // connect errors
+ atomic_t err_other; // other socket errors
+ };
+
+ struct netflow_aggr_n {
+ struct list_head list;
++ atomic_t usage;
+ __u32 mask;
+ __u32 addr;
+ __u32 aggr_mask;
+@@ -128,6 +273,7 @@ struct netflow_aggr_n {
+
+ struct netflow_aggr_p {
+ struct list_head list;
++ atomic_t usage;
+ __u16 port1;
+ __u16 port2;
+ __u16 aggr_port;
+diff --git a/libipt_NETFLOW.c b/libipt_NETFLOW.c
+index d85b6d9..a0f9e5d 100644
+--- a/libipt_NETFLOW.c
++++ b/libipt_NETFLOW.c
+@@ -58,24 +58,24 @@
+ #define _IPT_IP struct ipt_ip
+ #endif
+
++#ifndef IPTABLES_VERSION
++#define IPTABLES_VERSION XTABLES_VERSION
++#endif
++
+ static struct option opts[] = {
+- {0}
++ { 0 }
+ };
+
+ static void help(void)
+ {
+- printf( "NETFLOW target\n");
++ printf("NETFLOW target\n");
+ }
+
+-//static int parse(int c, char **argv, int invert, unsigned int *flags,
+-// const _IPT_ENTRY *entry,
+-// struct ipt_entry_target **target)
+ static int parse(int c, char **argv, int invert, unsigned int *flags,
+ const _IPT_ENTRY *entry,
+ struct ipt_entry_target **targetinfo)
+
+ {
+-
+ return 1;
+ }
+
+@@ -95,16 +95,9 @@ static void print(const _IPT_IP *ip,
+ }
+
+ static struct iptables_target netflow = {
+-#ifdef MOD140
+- .family = AF_INET,
+-#endif
+ .next = NULL,
+ .name = "NETFLOW",
+-#ifdef XTABLES_VERSION
+- .version = XTABLES_VERSION,
+-#else
+ .version = IPTABLES_VERSION,
+-#endif
+ .size = IPT_ALIGN(0),
+ .userspacesize = IPT_ALIGN(0),
+ .help = &help,
+diff --git a/murmur3.h b/murmur3.h
+new file mode 100644
+index 0000000..57a6006
+--- /dev/null
++++ b/murmur3.h
+@@ -0,0 +1,42 @@
++/* MurmurHash3, based on https://code.google.com/p/smhasher of Austin Appleby. */
++
++static __always_inline uint32_t rotl32(const uint32_t x, const int8_t r)
++{
++ return (x << r) | (x >> (32 - r));
++}
++
++static __always_inline uint32_t fmix32(register uint32_t h)
++{
++ h ^= h >> 16;
++ h *= 0x85ebca6b;
++ h ^= h >> 13;
++ h *= 0xc2b2ae35;
++ h ^= h >> 16;
++ return h;
++}
++
++static inline uint32_t murmur3(const void *key, const uint32_t len, const uint32_t seed)
++{
++ const uint32_t c1 = 0xcc9e2d51;
++ const uint32_t c2 = 0x1b873593;
++ const uint32_t *blocks;
++ const uint8_t *tail;
++ register uint32_t h1 = seed;
++ uint32_t k1 = 0;
++ uint32_t i;
++
++ blocks = (const uint32_t *)key;
++ for (i = len / 4; i; --i) {
++ h1 ^= rotl32(*blocks++ * c1, 15) * c2;
++ h1 = rotl32(h1, 13) * 5 + 0xe6546b64;
++ }
++ tail = (const uint8_t*)blocks;
++ switch (len & 3) {
++ case 3: k1 ^= tail[2] << 16;
++ case 2: k1 ^= tail[1] << 8;
++ case 1: k1 ^= tail[0];
++ h1 ^= rotl32(k1 * c1, 15) * c2;
++ }
++ return fmix32(h1^ len);
++}
++
+diff --git a/raw_promisc_debian_squeeze6.patch b/raw_promisc_debian_squeeze6.patch
+new file mode 100644
+index 0000000..69d0d35
+--- /dev/null
++++ b/raw_promisc_debian_squeeze6.patch
+@@ -0,0 +1,37 @@
++
++ Short manual and patch for Debian Squeeze
++ suggested by Pavel Odintsov:
++
++On Thu, Dec 27, 2012 at 07:46:30PM +0400, Pavel Odintsov wrote:
++>
++> краткий мануал для патчинга Debian Squeeze ядра патчем promisc.
++>
++> cd /usr/src
++> apt-get install -y dpkg-dev
++> apt-get build-dep linux-image-2.6.32-5-amd64
++> cd linux-2.6-2.6.32/
++> apt-get source linux-image-2.6.32-5-amd64
++>
++> wget .... /root/raw_promisc_debian_squeeze6.patch
++> patch -p1 < raw_promisc_debian_squeeze6.patch
++> Накладываем патчи дебияна:
++> debian/rules source
++>
++> Запускаем сборку:
++> debian/rules binary
++>
++
++diff -rupN linux-2.6-2.6.32/net/ipv4/ip_input.c linux-2.6-2.6.32_promisc_raw//net/ipv4/ip_input.c
++--- linux-2.6-2.6.32/net/ipv4/ip_input.c 2009-12-03 04:51:21.000000000 +0100
+++++ linux-2.6-2.6.32_promisc_raw//net/ipv4/ip_input.c 2012-06-25 19:13:49.000000000 +0200
++@@ -383,8 +383,8 @@ int ip_rcv(struct sk_buff *skb, struct n
++ /* When the interface is in promisc. mode, drop all the crap
++ * that it receives, do not try to analyse it.
++ */
++- if (skb->pkt_type == PACKET_OTHERHOST)
++- goto drop;
+++ //if (skb->pkt_type == PACKET_OTHERHOST)
+++ // goto drop;
++
++
++ IP_UPD_PO_STATS_BH(dev_net(dev), IPSTATS_MIB_IN, skb->len);
diff --git a/testing/ipt-netflow/kernel-vs-userspace.patch b/testing/ipt-netflow/kernel-vs-userspace.patch
new file mode 100644
index 0000000000..8f9857fa8b
--- /dev/null
+++ b/testing/ipt-netflow/kernel-vs-userspace.patch
@@ -0,0 +1,56 @@
+diff --git a/configure b/configure
+index 3f10e2a..b43f024 100755
+--- a/configure
++++ b/configure
+@@ -256,6 +256,8 @@ do
+ --ipt-inc=*) IPTINC="$ac_optarg" ;;
+ --kver=*) KVERSION="$ac_optarg" ;;
+ --kdir=*) KDIR="$ac_optarg" ;;
++ --disable-kernel) NOKERNEL=1;;
++ --disable-ipt) NOIPT=1;;
+ --make) echo called from make ;;
+ --help) show_help ;;
+ -*) echo Invalid option: $ac_option; exit 1 ;;
+@@ -353,22 +355,26 @@ kernel_check_config() {
+ kconfig CONFIG_IP6_NF_IPTABLES "ip6tables target"
+ }
+
+-kernel_find_version #KVERSION
+-test "$KLIBMOD" || KLIBMOD=$KVERSION
+-echo "Kernel version: $KVERSION ($KHOW)"
+-kernel_find_source #KDIR
+-echo "Kernel sources: $KDIR ($KSHOW)"
+-kernel_check_consistency
+-kernel_check_config
+-
+-test "$IPTBIN" || IPTBIN=`which iptables`
+-
+-iptables_find_version #IPTVER
+-iptables_try_pkgconfig #try to configure from pkg-config
+-iptables_find_src #IPTSRC
+-iptables_src_version #check that IPTSRC match to IPTVER
+-iptables_inc #IPTINC
+-iptables_modules #IPTLIB
++if ! test "$NOKERNEL"; then
++ kernel_find_version #KVERSION
++ test "$KLIBMOD" || KLIBMOD=$KVERSION
++ echo "Kernel version: $KVERSION ($KHOW)"
++ kernel_find_source #KDIR
++ echo "Kernel sources: $KDIR ($KSHOW)"
++ kernel_check_consistency
++ kernel_check_config
++fi
++
++if ! test "$NOIPT"; then
++ test "$IPTBIN" || IPTBIN=`which iptables`
++
++ iptables_find_version #IPTVER
++ iptables_try_pkgconfig #try to configure from pkg-config
++ iptables_find_src #IPTSRC
++ iptables_src_version #check that IPTSRC match to IPTVER
++ iptables_inc #IPTINC
++ iptables_modules #IPTLIB
++fi
+
+ REPLACE="\
+ s!@KVERSION@!$KVERSION!;\