1
0
Fork 0
mirror of https://gitlab.alpinelinux.org/alpine/aports.git synced 2025-07-23 19:25:25 +03:00
aports/testing/ipt-netflow/git.patch
Natanael Copa 7b9d3a45f2 testing/ipt-netflow: fix build again
Use upstream source repo rather than Rush's github clone
2014-02-25 13:11:42 +00:00

4308 lines
133 KiB
Diff
Raw Blame History

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:
+>
+> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> Debian Squeeze <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 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
+> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:
+> debian/rules source
+>
+> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>:
+> 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);