diff --git a/share/man/man4/siftr.4 b/share/man/man4/siftr.4 index 131cc2ca0791..fefa67f8673f 100644 --- a/share/man/man4/siftr.4 +++ b/share/man/man4/siftr.4 @@ -1,746 +1,746 @@ .\" .\" Copyright (c) 2010 The FreeBSD Foundation .\" .\" Portions of this software were developed at the Centre for Advanced .\" Internet Architectures, Swinburne University of Technology, Melbourne, .\" Australia by Lawrence Stewart under sponsorship from the FreeBSD .\" Foundation. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions, and the following disclaimer, .\" without modification, immediately at the beginning of the file. .\" 2. The name of the author may not be used to endorse or promote products .\" derived from this software without specific prior written permission. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR .\" ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD$ .\" -.Dd May 27, 2023 +.Dd May 29, 2023 .Dt SIFTR 4 .Os .Sh NAME .Nm SIFTR .Nd Statistical Information For TCP Research .Sh SYNOPSIS To load the driver as a module at run-time, run the following command as root: .Bd -literal -offset indent kldload siftr .Ed .Pp Alternatively, to load the driver as a module at boot time, add the following line into the .Xr loader.conf 5 file: .Bd -literal -offset indent siftr_load="YES" .Ed .Sh DESCRIPTION The .Nm .Po .Em S Ns tatistical .Em I Ns nformation .Em F Ns or .Em T Ns CP .Em R Ns esearch .Pc kernel module logs a range of statistics on active TCP connections to a log file. It provides the ability to make highly granular measurements of TCP connection state, aimed at system administrators, developers and researchers. .Ss Compile-time Configuration The default operation of .Nm is to capture IPv4 TCP/IP packets. .Nm can be configured to support IPv4 and IPv6 by uncommenting: .Bd -literal -offset indent CFLAGS+=-DSIFTR_IPV6 .Ed .Pp in .Aq sys/modules/siftr/Makefile and recompiling. .Pp In the IPv4-only (default) mode, standard dotted decimal notation (e.g. "136.186.229.95") is used to format IPv4 addresses for logging. In IPv6 mode, standard dotted decimal notation is used to format IPv4 addresses, and standard colon-separated hex notation (see RFC 4291) is used to format IPv6 addresses (e.g. "fd00::2") for logging. .Ss Run-time Configuration .Nm utilises the .Xr sysctl 8 interface to export its configuration variables to user-space. The following variables are available: .Bl -tag -offset indent -width Va .It Va net.inet.siftr.enabled controls whether the module performs its measurements or not. By default, the value is set to 0, which means the module will not be taking any measurements. Having the module loaded with .Va net.inet.siftr.enabled set to 0 will have no impact on the performance of the network stack, as the packet filtering hooks are only inserted when .Va net.inet.siftr.enabled is set to 1. .El .Bl -tag -offset indent -width Va .It Va net.inet.siftr.ppl controls how many inbound/outbound packets for a given TCP connection will cause a log message to be generated for the connection. By default, the value is set to 1, which means the module will log a message for every packet of every TCP connection. The value can be set to any integer in the range [1,2^32], and can be changed at any time, even while the module is enabled. .El .Bl -tag -offset indent -width Va .It Va net.inet.siftr.logfile controls the path to the file that the module writes its log messages to. By default, the file /var/log/siftr.log is used. The path can be changed at any time, even while the module is enabled. .El .Bl -tag -offset indent -width Va .It Va net.inet.siftr.port_filter -controls on which source or destination port siftr should capture -.Nm . +controls on which source or destination port +.Nm +should capture. By default, the value is set to 0, which means all ports are eligible for logging. Set to any other value, only packets where either the source or destination port is equal to this number are logged. .El .Ss Log Format A typical .Nm log file will contain 3 different types of log message. All messages are written in plain ASCII text. .Pp Note: The .Qq \e present in the example log messages in this section indicates a line continuation and is not part of the actual log message. .Pp The first type of log message is written to the file when the module is enabled and starts collecting data from the running kernel. The text below shows an example module enable log. The fields are tab delimited key-value pairs which describe some basic information about the system. .Bd -literal -offset indent -enable_time_secs=1238556193 enable_time_usecs=462104 \\ -siftrver=1.2.2 hz=1000 tcp_rtt_scale=32 \\ -sysname=FreeBSD sysver=604000 ipmode=4 +enable_time_secs=1685191807 enable_time_usecs=160752 \\ +siftrver=1.3.0 sysname=FreeBSD sysver=1400089 ipmode=4 .Ed .Pp Field descriptions are as follows: .Bl -tag -offset indent -width Va .It Va enable_time_secs time at which the module was enabled, in seconds since the UNIX epoch. .El .Bl -tag -offset indent -width Va .It Va enable_time_usecs time at which the module was enabled, in microseconds since enable_time_secs. .El .Bl -tag -offset indent -width Va .It Va siftrver version of .Nm . .El .Bl -tag -offset indent -width Va .It Va sysname operating system name. .El .Bl -tag -offset indent -width Va .It Va sysver operating system version. .El .Bl -tag -offset indent -width Va .It Va ipmode IP mode as defined at compile time. An ipmode of "4" means IPv6 is not supported and IP addresses are logged in regular dotted quad format. An ipmode of "6" means IPv6 is supported, and IP addresses are logged in dotted quad or hex format, as described in the .Qq Compile-time Configuration subsection. .El .Pp The second type of log message is written to the file when a data log message is generated. The text below shows an example data log triggered by an IPv4 TCP/IP packet. The data is CSV formatted. .Bd -literal -offset indent -o,0xbec491a5,1238556193.463551,172.16.7.28,22,172.16.2.5,55931, \\ -1073725440,172312,34,66560,66608,8,1,4,1448,936,1,996,255, \\ -33304,208,66608,0,208,0 +o,1685191814.185109,10.1.1.2,32291,10.1.1.3,5001,1073725440, \\ +14480,2,65160,65700,7,9,4,1460,1000,1,16778209,230000,33580,0, \\ +65700,0,0,0,86707916,130 .Ed .Pp Field descriptions are as follows: .Bl -tag -offset indent -width Va .It Va 1 Direction of packet that triggered the log message. Either .Qq i for in, or .Qq o for out. .El .Bl -tag -offset indent -width Va .It Va 2 Time at which the packet that triggered the log message was processed by the .Xr pfil 9 hook function, in seconds and microseconds since the UNIX epoch. .El .Bl -tag -offset indent -width Va .It Va 3 The IPv4 or IPv6 address of the local host, in dotted quad (IPv4 packet) or colon-separated hex (IPv6 packet) notation. .El .Bl -tag -offset indent -width Va .It Va 4 The TCP port that the local host is communicating via. .El .Bl -tag -offset indent -width Va .It Va 5 The IPv4 or IPv6 address of the foreign host, in dotted quad (IPv4 packet) or colon-separated hex (IPv6 packet) notation. .El .Bl -tag -offset indent -width Va .It Va 6 The TCP port that the foreign host is communicating via. .El .Bl -tag -offset indent -width Va .It Va 7 The slow start threshold for the flow, in bytes. .El .Bl -tag -offset indent -width Va .It Va 8 The current congestion window for the flow, in bytes. .El .Bl -tag -offset indent -width Va .It Va 9 The current state of the t_flags2 field for the flow. .El .Bl -tag -offset indent -width Va .It Va 10 The current sending window for the flow, in bytes. -The post scaled value is reported, except during the initial handshake (first -few packets), during which time the unscaled value is reported. +The post scaled value is reported. .El .Bl -tag -offset indent -width Va .It Va 11 The current receive window for the flow, in bytes. The post scaled value is always reported. .El .Bl -tag -offset indent -width Va .It Va 12 The current window scaling factor for the sending window. .El .Bl -tag -offset indent -width Va .It Va 13 The current window scaling factor for the receiving window. .El .Bl -tag -offset indent -width Va .It Va 14 The current state of the TCP finite state machine, as defined in .Aq Pa netinet/tcp_fsm.h . .El .Bl -tag -offset indent -width Va .It Va 15 The maximum segment size for the flow, in bytes. .El .Bl -tag -offset indent -width Va .It Va 16 The current smoothed RTT estimate for the flow, in units of microsecond. .El .Bl -tag -offset indent -width Va .It Va 17 SACK enabled indicator. 1 if SACK enabled, 0 otherwise. .El .Bl -tag -offset indent -width Va .It Va 18 The current state of the TCP flags for the flow. See .Aq Pa netinet/tcp_var.h for information about the various flags. .El .Bl -tag -offset indent -width Va .It Va 19 The current retransmission timeout length for the flow, in units microsecond. .El .Bl -tag -offset indent -width Va .It Va 20 The current size of the socket send buffer in bytes. .El .Bl -tag -offset indent -width Va .It Va 21 The current number of bytes in the socket send buffer. .El .Bl -tag -offset indent -width Va .It Va 22 The current size of the socket receive buffer in bytes. .El .Bl -tag -offset indent -width Va .It Va 23 The current number of bytes in the socket receive buffer. .El .Bl -tag -offset indent -width Va .It Va 24 The current number of unacknowledged bytes in-flight. Bytes acknowledged via SACK are not excluded from this count. .El .Bl -tag -offset indent -width Va .It Va 25 The current number of segments in the reassembly queue. .El .Bl -tag -offset indent -width Va .It Va 26 Flowid for the connection. A caveat: Zero '0' either represents a valid flowid or a default value when it's not being set. There is no easy way to differentiate without looking at actual network interface card and drivers being used. .El .Bl -tag -offset indent -width Va .It Va 27 Flow type for the connection. Flowtype defines which protocol fields are hashed to produce the flowid. A complete listing is available in .Pa sys/mbuf.h under .Dv M_HASHTYPE_* . .El .Pp The third type of log message is written to the file when the module is disabled and ceases collecting data from the running kernel. The text below shows an example module disable log. The fields are tab delimited key-value pairs which provide statistics about operations since the module was most recently enabled. .Bd -literal -offset indent -disable_time_secs=1238556197 disable_time_usecs=933607 \\ -num_inbound_tcp_pkts=356 num_outbound_tcp_pkts=627 \\ -total_tcp_pkts=983 num_inbound_skipped_pkts_malloc=0 \\ -num_outbound_skipped_pkts_malloc=0 num_inbound_skipped_pkts_tcb=0 \\ -num_outbound_skipped_pkts_tcb=0 num_inbound_skipped_pkts_icb=0 \\ -num_outbound_skipped_pkts_icb=0 total_skipped_tcp_pkts=0 \\ -flow_list=172.16.7.28;22-172.16.2.5;55931, +disable_time_secs=1685191816 disable_time_usecs=629397 \\ +num_inbound_tcp_pkts=10 num_outbound_tcp_pkts=10 \\ +total_tcp_pkts=20 num_inbound_skipped_pkts_malloc=0 \\ +num_outbound_skipped_pkts_malloc=0 num_inbound_skipped_pkts_tcpcb=2 \\ +num_outbound_skipped_pkts_tcpcb=2 num_inbound_skipped_pkts_inpcb=0 \\ +num_outbound_skipped_pkts_inpcb=0 total_skipped_tcp_pkts=4 \\ +flow_list=10.1.1.2;32291-10.1.1.3;5001,10.1.1.2;58544-10.1.1.3;5001, .Ed .Pp Field descriptions are as follows: .Bl -tag -offset indent -width Va .It Va disable_time_secs Time at which the module was disabled, in seconds since the UNIX epoch. .El .Bl -tag -offset indent -width Va .It Va disable_time_usecs Time at which the module was disabled, in microseconds since disable_time_secs. .El .Bl -tag -offset indent -width Va .It Va num_inbound_tcp_pkts Number of TCP packets that traversed up the network stack. This only includes inbound TCP packets during the periods when .Nm was enabled. .El .Bl -tag -offset indent -width Va .It Va num_outbound_tcp_pkts Number of TCP packets that traversed down the network stack. This only includes outbound TCP packets during the periods when .Nm was enabled. .El .Bl -tag -offset indent -width Va .It Va total_tcp_pkts The summation of num_inbound_tcp_pkts and num_outbound_tcp_pkts. .El .Bl -tag -offset indent -width Va .It Va num_inbound_skipped_pkts_malloc Number of inbound packets that were not processed because of failed .Fn malloc calls. .El .Bl -tag -offset indent -width Va .It Va num_outbound_skipped_pkts_malloc Number of outbound packets that were not processed because of failed .Fn malloc calls. .El .Bl -tag -offset indent -width Va -.It Va num_inbound_skipped_pkts_tcb +.It Va num_inbound_skipped_pkts_tcpcb Number of inbound packets that were not processed because of failure to find the TCP control block associated with the packet. .El .Bl -tag -offset indent -width Va -.It Va num_outbound_skipped_pkts_tcb +.It Va num_outbound_skipped_pkts_tcpcb Number of outbound packets that were not processed because of failure to find the TCP control block associated with the packet. .El .Bl -tag -offset indent -width Va -.It Va num_inbound_skipped_pkts_icb +.It Va num_inbound_skipped_pkts_inpcb Number of inbound packets that were not processed because of failure to find the IP control block associated with the packet. .El .Bl -tag -offset indent -width Va -.It Va num_outbound_skipped_pkts_icb +.It Va num_outbound_skipped_pkts_inpcb Number of outbound packets that were not processed because of failure to find the IP control block associated with the packet. .El .Bl -tag -offset indent -width Va .It Va total_skipped_tcp_pkts The summation of all skipped packet counters. .El .Bl -tag -offset indent -width Va .It Va flow_list A CSV list of TCP flows that triggered data log messages to be generated since the module was loaded. Each flow entry in the CSV list is formatted as .Qq local_ip;local_port-foreign_ip;foreign_port . If there are no entries in the list (i.e., no data log messages were generated), the value will be blank. If there is at least one entry in the list, a trailing comma will always be present. .El .Pp The total number of data log messages found in the log file for a module enable/disable cycle should equate to total_tcp_pkts - total_skipped_tcp_pkts. .Sh IMPLEMENTATION NOTES .Nm hooks into the network stack using the .Xr pfil 9 interface. In its current incarnation, it hooks into the AF_INET/AF_INET6 (IPv4/IPv6) .Xr pfil 9 filtering points, which means it sees packets at the IP layer of the network stack. This means that TCP packets inbound to the stack are intercepted before they have been processed by the TCP layer. Packets outbound from the stack are intercepted after they have been processed by the TCP layer. .Pp The diagram below illustrates how .Nm inserts itself into the stack. .Bd -literal -offset indent ---------------------------------- Upper Layers ---------------------------------- ^ | | | | | | v TCP in TCP out ---------------------------------- ^ | |________ _________| | | | v --------- | SIFTR | --------- ^ | ________| |__________ | | | v IPv{4/6} in IPv{4/6} out ---------------------------------- ^ | | | | v Layer 2 in Layer 2 out ---------------------------------- Physical Layer ---------------------------------- .Ed .Pp .Nm uses the .Xr alq 9 interface to manage writing data to disk. .Pp At first glance, you might mistakenly think that .Nm extracts information from individual TCP packets. This is not the case. .Nm uses TCP packet events (inbound and outbound) for each TCP flow originating from the system to trigger a dump of the state of the TCP control block for that flow. With the PPL set to 1, we are in effect sampling each TCP flow's control block state as frequently as flow packets enter/leave the system. For example, setting PPL to 2 halves the sampling rate i.e., every second flow packet (inbound OR outbound) causes a dump of the control block state. .Pp The distinction between interrogating individual packets versus interrogating the control block is important, because .Nm does not remove the need for packet capturing tools like .Xr tcpdump 1 . .Nm allows you to correlate and observe the cause-and-affect relationship between what you see on the wire (captured using a tool like .Xr tcpdump 1 Ns ) and changes in the TCP control block corresponding to the flow of interest. It is therefore useful to use .Nm and a tool like .Xr tcpdump 1 to gather the necessary data to piece together the complete picture. Use of either tool on its own will not be able to provide all of the necessary data. .Pp As a result of needing to interrogate the TCP control block, certain packets during the lifecycle of a connection are unable to trigger a .Nm log message. -The initial handshake takes place without the existence of a control block and -the final ACK is exchanged when the connection is in the TIMEWAIT state. +The initial handshake takes place without the existence of a control block or +the complete initialization of the control block, and the final ACK is +exchanged when the connection is in the TIMEWAIT state. .Pp .Nm was designed to minimise the delay introduced to packets traversing the network stack. This design called for a highly optimised and minimal hook function that extracted the minimal details necessary whilst holding the packet up, and passing these details to another thread for actual processing and logging. .Pp This multithreaded design does introduce some contention issues when accessing the data structure shared between the threads of operation. When the hook function tries to place details in the structure, it must first acquire an exclusive lock. Likewise, when the processing thread tries to read details from the structure, it must also acquire an exclusive lock to do so. If one thread holds the lock, the other must wait before it can obtain it. This does introduce some additional bounded delay into the kernel's packet processing code path. .Pp In some cases (e.g., low memory, connection termination), TCP packets that enter the .Nm .Xr pfil 9 hook function will not trigger a log message to be generated. .Nm refers to this outcome as a .Qq skipped packet . Note that .Nm always ensures that packets are allowed to continue through the stack, even if they could not successfully trigger a data log message. .Nm will therefore not introduce any packet loss for TCP/IP packets traversing the network stack. .Ss Important Behaviours The behaviour of a log file path change whilst the module is enabled is as follows: .Bl -enum .It Attempt to open the new file path for writing. If this fails, the path change will fail and the existing path will continue to be used. .It Assuming the new path is valid and opened successfully: .Bl -dash .It Flush all pending log messages to the old file path. .It Close the old file path. .It Switch the active log file pointer to point at the new file path. .It Commence logging to the new file. .El .El .Pp During the time between the flush of pending log messages to the old file and commencing logging to the new file, new log messages will still be generated and buffered. As soon as the new file path is ready for writing, the accumulated log messages will be written out to the file. .Sh EXAMPLES To enable the module's operations, run the following command as root: sysctl net.inet.siftr.enabled=1 .Pp To change the granularity of log messages such that 1 log message is generated for every 10 TCP packets per connection, run the following command as root: sysctl net.inet.siftr.ppl=10 .Pp To change the log file location to /tmp/siftr.log, run the following command as root: sysctl net.inet.siftr.logfile=/tmp/siftr.log .Sh SEE ALSO .Xr tcpdump 1 , .Xr tcp 4 , .Xr sysctl 8 , .Xr alq 9 , .Xr pfil 9 .Sh ACKNOWLEDGEMENTS Development of this software was made possible in part by grants from the Cisco University Research Program Fund at Community Foundation Silicon Valley, and the FreeBSD Foundation. .Sh HISTORY .Nm first appeared in .Fx 7.4 and .Fx 8.2 . .Pp .Nm was first released in 2007 by Lawrence Stewart and James Healy whilst working on the NewTCP research project at Swinburne University of Technology's Centre for Advanced Internet Architectures, Melbourne, Australia, which was made possible in part by a grant from the Cisco University Research Program Fund at Community Foundation Silicon Valley. More details are available at: .Pp http://caia.swin.edu.au/urp/newtcp/ .Pp Work on .Nm v1.2.x was sponsored by the FreeBSD Foundation as part of the .Qq Enhancing the FreeBSD TCP Implementation project 2008-2009. More details are available at: .Pp https://www.freebsdfoundation.org/ .Pp http://caia.swin.edu.au/freebsd/etcp09/ .Sh AUTHORS .An -nosplit .Nm was written by .An Lawrence Stewart Aq Mt lstewart@FreeBSD.org and .An James Healy Aq Mt jimmy@deefa.com . .Pp This manual page was written by .An Lawrence Stewart Aq Mt lstewart@FreeBSD.org . .Sh BUGS Current known limitations and any relevant workarounds are outlined below: .Bl -dash .It The internal queue used to pass information between the threads of operation is currently unbounded. This allows .Nm to cope with bursty network traffic, but sustained high packet-per-second traffic can cause exhaustion of kernel memory if the processing thread cannot keep up with the packet rate. .It If using .Nm on a machine that is also running other modules utilising the .Xr pfil 9 framework e.g. .Xr dummynet 4 , .Xr ipfw 8 , .Xr pf 4 Ns , the order in which you load the modules is important. You should kldload the other modules first, as this will ensure TCP packets undergo any necessary manipulations before .Nm .Qq sees and processes them. .It There is a known, harmless lock order reversal warning between the .Xr pfil 9 mutex and tcbinfo TCP lock reported by .Xr witness 4 when .Nm is enabled in a kernel compiled with .Xr witness 4 support. .It There is no way to filter which TCP flows you wish to capture data for. Post processing is required to separate out data belonging to particular flows of interest. .It The module does not detect deletion of the log file path. New log messages will simply be lost if the log file being used by .Nm is deleted whilst the module is set to use the file. Switching to a new log file using the .Em net.inet.siftr.logfile variable will create the new file and allow log messages to begin being written to disk again. The new log file path must differ from the path to the deleted file. .It The hash table used within the code is sized to hold 65536 flows. This is not a hard limit, because chaining is used to handle collisions within the hash table structure. However, we suspect (based on analogies with other hash table performance data) that the hash table look up performance (and therefore the module's packet processing performance) will degrade in an exponential manner as the number of unique flows handled in a module enable/disable cycle approaches and surpasses 65536. .It There is no garbage collection performed on the flow hash table. The only way currently to flush it is to disable .Nm . .It The PPL variable applies to packets that make it into the processing thread, not total packets received in the hook function. Packets are skipped before the PPL variable is applied, which means there may be a slight discrepancy in the triggering of log messages. For example, if PPL was set to 10, and the 8th packet since the last log message is skipped, the 11th packet will actually trigger the log message to be generated. This is discussed in greater depth in CAIA technical report 070824A. .It At the time of writing, there was no simple way to hook into the TCP layer to intercept packets. .Nm Ap s use of IP layer hook points means all IP traffic will be processed by the .Nm .Xr pfil 9 hook function, which introduces minor, but nonetheless unnecessary packet delay and processing overhead on the system for non-TCP packets as well. Hooking in at the IP layer is also not ideal from the data gathering point of view. Packets traversing up the stack will be intercepted and cause a log message generation BEFORE they have been processed by the TCP layer, which means we cannot observe the cause-and-affect relationship between inbound events and the corresponding TCP control block as precisely as could be. Ideally, .Nm should intercept packets after they have been processed by the TCP layer i.e. intercept packets coming up the stack after they have been processed by .Fn tcp_input , and intercept packets coming down the stack after they have been processed by .Fn tcp_output . The current code still gives satisfactory granularity though, as inbound events tend to trigger outbound events, allowing the cause-and-effect to be observed indirectly by capturing the state on outbound events as well. .It The .Qq inflight bytes value logged by .Nm does not take into account bytes that have been .No SACK Ap ed by the receiving host. .El diff --git a/sys/netinet/siftr.c b/sys/netinet/siftr.c index 9cad93b3ecf7..2741553ed757 100644 --- a/sys/netinet/siftr.c +++ b/sys/netinet/siftr.c @@ -1,1343 +1,1341 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2007-2009 * Swinburne University of Technology, Melbourne, Australia. * Copyright (c) 2009-2010, The FreeBSD Foundation * All rights reserved. * * Portions of this software were developed at the Centre for Advanced * Internet Architectures, Swinburne University of Technology, Melbourne, * Australia by Lawrence Stewart under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /****************************************************** * Statistical Information For TCP Research (SIFTR) * * A FreeBSD kernel module that adds very basic intrumentation to the * TCP stack, allowing internal stats to be recorded to a log file * for experimental, debugging and performance analysis purposes. * * SIFTR was first released in 2007 by James Healy and Lawrence Stewart whilst * working on the NewTCP research project at Swinburne University of * Technology's Centre for Advanced Internet Architectures, Melbourne, * Australia, which was made possible in part by a grant from the Cisco * University Research Program Fund at Community Foundation Silicon Valley. * More details are available at: * http://caia.swin.edu.au/urp/newtcp/ * * Work on SIFTR v1.2.x was sponsored by the FreeBSD Foundation as part of * the "Enhancing the FreeBSD TCP Implementation" project 2008-2009. * More details are available at: * http://www.freebsdfoundation.org/ * http://caia.swin.edu.au/freebsd/etcp09/ * * Lawrence Stewart is the current maintainer, and all contact regarding * SIFTR should be directed to him via email: lastewart@swin.edu.au * * Initial release date: June 2007 * Most recent update: September 2010 ******************************************************/ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SIFTR_IPV6 #include #include #include #include #endif /* SIFTR_IPV6 */ #include /* * Three digit version number refers to X.Y.Z where: * X is the major version number * Y is bumped to mark backwards incompatible changes * Z is bumped to mark backwards compatible changes */ #define V_MAJOR 1 #define V_BACKBREAK 3 #define V_BACKCOMPAT 0 #define MODVERSION __CONCAT(V_MAJOR, __CONCAT(V_BACKBREAK, V_BACKCOMPAT)) #define MODVERSION_STR __XSTRING(V_MAJOR) "." __XSTRING(V_BACKBREAK) "." \ __XSTRING(V_BACKCOMPAT) #define HOOK 0 #define UNHOOK 1 #define SIFTR_EXPECTED_MAX_TCP_FLOWS 65536 #define SYS_NAME "FreeBSD" #define PACKET_TAG_SIFTR 100 #define PACKET_COOKIE_SIFTR 21749576 #define SIFTR_LOG_FILE_MODE 0644 #define SIFTR_DISABLE 0 #define SIFTR_ENABLE 1 /* * Hard upper limit on the length of log messages. Bump this up if you add new * data fields such that the line length could exceed the below value. */ #define MAX_LOG_MSG_LEN 300 /* XXX: Make this a sysctl tunable. */ #define SIFTR_ALQ_BUFLEN (1000*MAX_LOG_MSG_LEN) #ifdef SIFTR_IPV6 #define SIFTR_IPMODE 6 #else #define SIFTR_IPMODE 4 #endif static MALLOC_DEFINE(M_SIFTR, "siftr", "dynamic memory used by SIFTR"); static MALLOC_DEFINE(M_SIFTR_PKTNODE, "siftr_pktnode", "SIFTR pkt_node struct"); static MALLOC_DEFINE(M_SIFTR_HASHNODE, "siftr_hashnode", "SIFTR flow_hash_node struct"); /* Used as links in the pkt manager queue. */ struct pkt_node { /* Timestamp of pkt as noted in the pfil hook. */ struct timeval tval; /* Direction pkt is travelling. */ enum { DIR_IN = 0, DIR_OUT = 1, } direction; /* Congestion Window (bytes). */ uint32_t snd_cwnd; /* Sending Window (bytes). */ uint32_t snd_wnd; /* Receive Window (bytes). */ uint32_t rcv_wnd; /* More tcpcb flags storage */ uint32_t t_flags2; /* Slow Start Threshold (bytes). */ uint32_t snd_ssthresh; /* Current state of the TCP FSM. */ int conn_state; /* Max Segment Size (bytes). */ u_int max_seg_size; /* Smoothed RTT (usecs). */ uint32_t srtt; /* Is SACK enabled? */ u_char sack_enabled; /* Window scaling for snd window. */ u_char snd_scale; /* Window scaling for recv window. */ u_char rcv_scale; /* TCP control block flags. */ u_int flags; /* Retransmission timeout (usec). */ uint32_t rto; /* Size of the TCP send buffer in bytes. */ u_int snd_buf_hiwater; /* Current num bytes in the send socket buffer. */ u_int snd_buf_cc; /* Size of the TCP receive buffer in bytes. */ u_int rcv_buf_hiwater; /* Current num bytes in the receive socket buffer. */ u_int rcv_buf_cc; /* Number of bytes inflight that we are waiting on ACKs for. */ u_int sent_inflight_bytes; /* Number of segments currently in the reassembly queue. */ int t_segqlen; /* Flowid for the connection. */ u_int flowid; /* Flow type for the connection. */ u_int flowtype; /* Link to next pkt_node in the list. */ STAILQ_ENTRY(pkt_node) nodes; }; struct flow_info { #ifdef SIFTR_IPV6 char laddr[INET6_ADDRSTRLEN]; /* local IP address */ char faddr[INET6_ADDRSTRLEN]; /* foreign IP address */ #else char laddr[INET_ADDRSTRLEN]; /* local IP address */ char faddr[INET_ADDRSTRLEN]; /* foreign IP address */ #endif uint16_t lport; /* local TCP port */ uint16_t fport; /* foreign TCP port */ uint8_t ipver; /* IP version */ uint32_t key; /* flowid of the connection */ }; struct flow_hash_node { uint16_t counter; struct flow_info const_info; /* constant connection info */ LIST_ENTRY(flow_hash_node) nodes; }; struct siftr_stats { /* # TCP pkts seen by the SIFTR PFIL hooks, including any skipped. */ uint64_t n_in; uint64_t n_out; /* # pkts skipped due to failed malloc calls. */ uint32_t nskip_in_malloc; uint32_t nskip_out_malloc; /* # pkts skipped due to failed inpcb lookups. */ uint32_t nskip_in_inpcb; uint32_t nskip_out_inpcb; /* # pkts skipped due to failed tcpcb lookups. */ uint32_t nskip_in_tcpcb; uint32_t nskip_out_tcpcb; /* # pkts skipped due to stack reinjection. */ uint32_t nskip_in_dejavu; uint32_t nskip_out_dejavu; }; DPCPU_DEFINE_STATIC(struct siftr_stats, ss); static volatile unsigned int siftr_exit_pkt_manager_thread = 0; static unsigned int siftr_enabled = 0; static unsigned int siftr_pkts_per_log = 1; static uint16_t siftr_port_filter = 0; /* static unsigned int siftr_binary_log = 0; */ static char siftr_logfile[PATH_MAX] = "/var/log/siftr.log"; static char siftr_logfile_shadow[PATH_MAX] = "/var/log/siftr.log"; static u_long siftr_hashmask; STAILQ_HEAD(pkthead, pkt_node) pkt_queue = STAILQ_HEAD_INITIALIZER(pkt_queue); LIST_HEAD(listhead, flow_hash_node) *counter_hash; static int wait_for_pkt; static struct alq *siftr_alq = NULL; static struct mtx siftr_pkt_queue_mtx; static struct mtx siftr_pkt_mgr_mtx; static struct thread *siftr_pkt_manager_thr = NULL; static char direction[2] = {'i','o'}; /* Required function prototypes. */ static int siftr_sysctl_enabled_handler(SYSCTL_HANDLER_ARGS); static int siftr_sysctl_logfile_name_handler(SYSCTL_HANDLER_ARGS); /* Declare the net.inet.siftr sysctl tree and populate it. */ SYSCTL_DECL(_net_inet_siftr); SYSCTL_NODE(_net_inet, OID_AUTO, siftr, CTLFLAG_RW | CTLFLAG_MPSAFE, NULL, "siftr related settings"); SYSCTL_PROC(_net_inet_siftr, OID_AUTO, enabled, CTLTYPE_UINT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &siftr_enabled, 0, &siftr_sysctl_enabled_handler, "IU", "switch siftr module operations on/off"); SYSCTL_PROC(_net_inet_siftr, OID_AUTO, logfile, CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_NEEDGIANT, &siftr_logfile_shadow, sizeof(siftr_logfile_shadow), &siftr_sysctl_logfile_name_handler, "A", "file to save siftr log messages to"); SYSCTL_UINT(_net_inet_siftr, OID_AUTO, ppl, CTLFLAG_RW, &siftr_pkts_per_log, 1, "number of packets between generating a log message"); SYSCTL_U16(_net_inet_siftr, OID_AUTO, port_filter, CTLFLAG_RW, &siftr_port_filter, 0, "enable packet filter on a TCP port"); /* XXX: TODO SYSCTL_UINT(_net_inet_siftr, OID_AUTO, binary, CTLFLAG_RW, &siftr_binary_log, 0, "write log files in binary instead of ascii"); */ /* Begin functions. */ static inline struct flow_hash_node * siftr_find_flow(struct listhead *counter_list, uint32_t id) { struct flow_hash_node *hash_node; /* * If the list is not empty i.e. the hash index has * been used by another flow previously. */ if (LIST_FIRST(counter_list) != NULL) { /* * Loop through the hash nodes in the list. * There should normally only be 1 hash node in the list. */ LIST_FOREACH(hash_node, counter_list, nodes) { /* * Check if the key for the pkt we are currently * processing is the same as the key stored in the * hash node we are currently processing. * If they are the same, then we've found the * hash node that stores the counter for the flow * the pkt belongs to. */ if (hash_node->const_info.key == id) { return hash_node; } } } return NULL; } static inline struct flow_hash_node * siftr_new_hash_node(struct flow_info info, int dir, struct siftr_stats *ss) { struct flow_hash_node *hash_node; struct listhead *counter_list; counter_list = counter_hash + (info.key & siftr_hashmask); /* Create a new hash node to store the flow's constant info. */ hash_node = malloc(sizeof(struct flow_hash_node), M_SIFTR_HASHNODE, M_NOWAIT|M_ZERO); if (hash_node != NULL) { /* Initialise our new hash node list entry. */ hash_node->counter = 0; hash_node->const_info = info; LIST_INSERT_HEAD(counter_list, hash_node, nodes); return hash_node; } else { /* malloc failed */ if (dir == DIR_IN) ss->nskip_in_malloc++; else ss->nskip_out_malloc++; return NULL; } } static void siftr_process_pkt(struct pkt_node * pkt_node) { struct flow_hash_node *hash_node; struct listhead *counter_list; struct ale *log_buf; if (pkt_node->flowid == 0) { panic("%s: flowid not available", __func__); } counter_list = counter_hash + (pkt_node->flowid & siftr_hashmask); hash_node = siftr_find_flow(counter_list, pkt_node->flowid); if (hash_node == NULL) { return; } log_buf = alq_getn(siftr_alq, MAX_LOG_MSG_LEN, ALQ_WAITOK); if (log_buf == NULL) return; /* Should only happen if the ALQ is shutting down. */ /* Construct a log message. */ log_buf->ae_bytesused = snprintf(log_buf->ae_data, MAX_LOG_MSG_LEN, "%c,%jd.%06ld,%s,%hu,%s,%hu,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u,%u," "%u,%u,%u,%u,%u,%u,%u,%u\n", direction[pkt_node->direction], pkt_node->tval.tv_sec, pkt_node->tval.tv_usec, hash_node->const_info.laddr, hash_node->const_info.lport, hash_node->const_info.faddr, hash_node->const_info.fport, pkt_node->snd_ssthresh, pkt_node->snd_cwnd, pkt_node->t_flags2, pkt_node->snd_wnd, pkt_node->rcv_wnd, pkt_node->snd_scale, pkt_node->rcv_scale, pkt_node->conn_state, pkt_node->max_seg_size, pkt_node->srtt, pkt_node->sack_enabled, pkt_node->flags, pkt_node->rto, pkt_node->snd_buf_hiwater, pkt_node->snd_buf_cc, pkt_node->rcv_buf_hiwater, pkt_node->rcv_buf_cc, pkt_node->sent_inflight_bytes, pkt_node->t_segqlen, pkt_node->flowid, pkt_node->flowtype); alq_post_flags(siftr_alq, log_buf, 0); } static void siftr_pkt_manager_thread(void *arg) { STAILQ_HEAD(pkthead, pkt_node) tmp_pkt_queue = STAILQ_HEAD_INITIALIZER(tmp_pkt_queue); struct pkt_node *pkt_node, *pkt_node_temp; uint8_t draining; draining = 2; mtx_lock(&siftr_pkt_mgr_mtx); /* draining == 0 when queue has been flushed and it's safe to exit. */ while (draining) { /* * Sleep until we are signalled to wake because thread has * been told to exit or until 1 tick has passed. */ mtx_sleep(&wait_for_pkt, &siftr_pkt_mgr_mtx, PWAIT, "pktwait", 1); /* Gain exclusive access to the pkt_node queue. */ mtx_lock(&siftr_pkt_queue_mtx); /* * Move pkt_queue to tmp_pkt_queue, which leaves * pkt_queue empty and ready to receive more pkt_nodes. */ STAILQ_CONCAT(&tmp_pkt_queue, &pkt_queue); /* * We've finished making changes to the list. Unlock it * so the pfil hooks can continue queuing pkt_nodes. */ mtx_unlock(&siftr_pkt_queue_mtx); /* * We can't hold a mutex whilst calling siftr_process_pkt * because ALQ might sleep waiting for buffer space. */ mtx_unlock(&siftr_pkt_mgr_mtx); /* Flush all pkt_nodes to the log file. */ STAILQ_FOREACH_SAFE(pkt_node, &tmp_pkt_queue, nodes, pkt_node_temp) { siftr_process_pkt(pkt_node); STAILQ_REMOVE_HEAD(&tmp_pkt_queue, nodes); free(pkt_node, M_SIFTR_PKTNODE); } KASSERT(STAILQ_EMPTY(&tmp_pkt_queue), ("SIFTR tmp_pkt_queue not empty after flush")); mtx_lock(&siftr_pkt_mgr_mtx); /* * If siftr_exit_pkt_manager_thread gets set during the window * where we are draining the tmp_pkt_queue above, there might * still be pkts in pkt_queue that need to be drained. * Allow one further iteration to occur after * siftr_exit_pkt_manager_thread has been set to ensure * pkt_queue is completely empty before we kill the thread. * * siftr_exit_pkt_manager_thread is set only after the pfil * hooks have been removed, so only 1 extra iteration * is needed to drain the queue. */ if (siftr_exit_pkt_manager_thread) draining--; } mtx_unlock(&siftr_pkt_mgr_mtx); /* Calls wakeup on this thread's struct thread ptr. */ kthread_exit(); } /* * Check if a given mbuf has the SIFTR mbuf tag. If it does, log the fact that * it's a reinjected packet and return. If it doesn't, tag the mbuf and return. * Return value >0 means the caller should skip processing this mbuf. */ static inline int siftr_chkreinject(struct mbuf *m, int dir, struct siftr_stats *ss) { if (m_tag_locate(m, PACKET_COOKIE_SIFTR, PACKET_TAG_SIFTR, NULL) != NULL) { if (dir == PFIL_IN) ss->nskip_in_dejavu++; else ss->nskip_out_dejavu++; return (1); } else { struct m_tag *tag = m_tag_alloc(PACKET_COOKIE_SIFTR, PACKET_TAG_SIFTR, 0, M_NOWAIT); if (tag == NULL) { if (dir == PFIL_IN) ss->nskip_in_malloc++; else ss->nskip_out_malloc++; return (1); } m_tag_prepend(m, tag); } return (0); } /* * Look up an inpcb for a packet. Return the inpcb pointer if found, or NULL * otherwise. */ static inline struct inpcb * siftr_findinpcb(int ipver, struct ip *ip, struct mbuf *m, uint16_t sport, uint16_t dport, int dir, struct siftr_stats *ss) { struct inpcb *inp; /* We need the tcbinfo lock. */ INP_INFO_WUNLOCK_ASSERT(&V_tcbinfo); if (dir == PFIL_IN) inp = (ipver == INP_IPV4 ? in_pcblookup(&V_tcbinfo, ip->ip_src, sport, ip->ip_dst, dport, INPLOOKUP_RLOCKPCB, m->m_pkthdr.rcvif) : #ifdef SIFTR_IPV6 in6_pcblookup(&V_tcbinfo, &((struct ip6_hdr *)ip)->ip6_src, sport, &((struct ip6_hdr *)ip)->ip6_dst, dport, INPLOOKUP_RLOCKPCB, m->m_pkthdr.rcvif) #else NULL #endif ); else inp = (ipver == INP_IPV4 ? in_pcblookup(&V_tcbinfo, ip->ip_dst, dport, ip->ip_src, sport, INPLOOKUP_RLOCKPCB, m->m_pkthdr.rcvif) : #ifdef SIFTR_IPV6 in6_pcblookup(&V_tcbinfo, &((struct ip6_hdr *)ip)->ip6_dst, dport, &((struct ip6_hdr *)ip)->ip6_src, sport, INPLOOKUP_RLOCKPCB, m->m_pkthdr.rcvif) #else NULL #endif ); /* If we can't find the inpcb, bail. */ if (inp == NULL) { if (dir == PFIL_IN) ss->nskip_in_inpcb++; else ss->nskip_out_inpcb++; } return (inp); } static inline uint32_t siftr_get_flowid(struct inpcb *inp, int ipver, uint32_t *phashtype) { if (inp->inp_flowid == 0) { #ifdef SIFTR_IPV6 if (ipver == INP_IPV6) { return fib6_calc_packet_hash(&inp->in6p_laddr, &inp->in6p_faddr, inp->inp_lport, inp->inp_fport, IPPROTO_TCP, phashtype); } else #endif { return fib4_calc_packet_hash(inp->inp_laddr, inp->inp_faddr, inp->inp_lport, inp->inp_fport, IPPROTO_TCP, phashtype); } } else { *phashtype = inp->inp_flowtype; return inp->inp_flowid; } } static inline void siftr_siftdata(struct pkt_node *pn, struct inpcb *inp, struct tcpcb *tp, int ipver, int dir, int inp_locally_locked) { pn->snd_cwnd = tp->snd_cwnd; pn->snd_wnd = tp->snd_wnd; pn->rcv_wnd = tp->rcv_wnd; pn->t_flags2 = tp->t_flags2; pn->snd_ssthresh = tp->snd_ssthresh; pn->snd_scale = tp->snd_scale; pn->rcv_scale = tp->rcv_scale; pn->conn_state = tp->t_state; pn->max_seg_size = tp->t_maxseg; pn->srtt = ((uint64_t)tp->t_srtt * tick) >> TCP_RTT_SHIFT; pn->sack_enabled = (tp->t_flags & TF_SACK_PERMIT) != 0; pn->flags = tp->t_flags; pn->rto = tp->t_rxtcur * tick; pn->snd_buf_hiwater = inp->inp_socket->so_snd.sb_hiwat; pn->snd_buf_cc = sbused(&inp->inp_socket->so_snd); pn->rcv_buf_hiwater = inp->inp_socket->so_rcv.sb_hiwat; pn->rcv_buf_cc = sbused(&inp->inp_socket->so_rcv); pn->sent_inflight_bytes = tp->snd_max - tp->snd_una; pn->t_segqlen = tp->t_segqlen; /* We've finished accessing the tcb so release the lock. */ if (inp_locally_locked) INP_RUNLOCK(inp); pn->direction = (dir == PFIL_IN ? DIR_IN : DIR_OUT); /* * Significantly more accurate than using getmicrotime(), but slower! * Gives true microsecond resolution at the expense of a hit to * maximum pps throughput processing when SIFTR is loaded and enabled. */ microtime(&pn->tval); TCP_PROBE1(siftr, &pn); } /* * pfil hook that is called for each IPv4 packet making its way through the * stack in either direction. * The pfil subsystem holds a non-sleepable mutex somewhere when * calling our hook function, so we can't sleep at all. * It's very important to use the M_NOWAIT flag with all function calls * that support it so that they won't sleep, otherwise you get a panic. */ static pfil_return_t siftr_chkpkt(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp) { struct pkt_node *pn; struct ip *ip; struct tcphdr *th; struct tcpcb *tp; struct siftr_stats *ss; unsigned int ip_hl; int inp_locally_locked, dir; uint32_t hash_id, hash_type; struct listhead *counter_list; struct flow_hash_node *hash_node; inp_locally_locked = 0; dir = PFIL_DIR(flags); ss = DPCPU_PTR(ss); /* * m_pullup is not required here because ip_{input|output} * already do the heavy lifting for us. */ ip = mtod(*m, struct ip *); /* Only continue processing if the packet is TCP. */ if (ip->ip_p != IPPROTO_TCP) goto ret; /* * Create a tcphdr struct starting at the correct offset * in the IP packet. ip->ip_hl gives the ip header length * in 4-byte words, so multiply it to get the size in bytes. */ ip_hl = (ip->ip_hl << 2); th = (struct tcphdr *)((caddr_t)ip + ip_hl); /* * Only pkts selected by the tcp port filter * can be inserted into the pkt_queue */ if ((siftr_port_filter != 0) && (siftr_port_filter != ntohs(th->th_sport)) && (siftr_port_filter != ntohs(th->th_dport))) { goto ret; } /* * If a kernel subsystem reinjects packets into the stack, our pfil * hook will be called multiple times for the same packet. * Make sure we only process unique packets. */ if (siftr_chkreinject(*m, dir, ss)) goto ret; if (dir == PFIL_IN) ss->n_in++; else ss->n_out++; /* * If the pfil hooks don't provide a pointer to the * inpcb, we need to find it ourselves and lock it. */ if (!inp) { /* Find the corresponding inpcb for this pkt. */ inp = siftr_findinpcb(INP_IPV4, ip, *m, th->th_sport, th->th_dport, dir, ss); if (inp == NULL) goto ret; else inp_locally_locked = 1; } INP_LOCK_ASSERT(inp); /* Find the TCP control block that corresponds with this packet */ tp = intotcpcb(inp); /* * If we can't find the TCP control block (happens occasionaly for a * packet sent during the shutdown phase of a TCP connection), or the * TCP control block has not initialized (happens during TCPS_SYN_SENT), * bail. */ if (tp == NULL || tp->t_state < TCPS_ESTABLISHED) { if (dir == PFIL_IN) ss->nskip_in_tcpcb++; else ss->nskip_out_tcpcb++; goto inp_unlock; } hash_id = siftr_get_flowid(inp, INP_IPV4, &hash_type); counter_list = counter_hash + (hash_id & siftr_hashmask); hash_node = siftr_find_flow(counter_list, hash_id); /* If this flow hasn't been seen before, we create a new entry. */ if (hash_node == NULL) { struct flow_info info; inet_ntoa_r(inp->inp_laddr, info.laddr); inet_ntoa_r(inp->inp_faddr, info.faddr); info.lport = ntohs(inp->inp_lport); info.fport = ntohs(inp->inp_fport); info.key = hash_id; info.ipver = INP_IPV4; hash_node = siftr_new_hash_node(info, dir, ss); } if (hash_node == NULL) { goto inp_unlock; } pn = malloc(sizeof(struct pkt_node), M_SIFTR_PKTNODE, M_NOWAIT|M_ZERO); if (pn == NULL) { if (dir == PFIL_IN) ss->nskip_in_malloc++; else ss->nskip_out_malloc++; goto inp_unlock; } pn->flowid = hash_id; pn->flowtype = hash_type; siftr_siftdata(pn, inp, tp, INP_IPV4, dir, inp_locally_locked); mtx_lock(&siftr_pkt_queue_mtx); STAILQ_INSERT_TAIL(&pkt_queue, pn, nodes); mtx_unlock(&siftr_pkt_queue_mtx); goto ret; inp_unlock: if (inp_locally_locked) INP_RUNLOCK(inp); ret: return (PFIL_PASS); } #ifdef SIFTR_IPV6 static pfil_return_t siftr_chkpkt6(struct mbuf **m, struct ifnet *ifp, int flags, void *ruleset __unused, struct inpcb *inp) { struct pkt_node *pn; struct ip6_hdr *ip6; struct tcphdr *th; struct tcpcb *tp; struct siftr_stats *ss; unsigned int ip6_hl; int inp_locally_locked, dir; uint32_t hash_id, hash_type; struct listhead *counter_list; struct flow_hash_node *hash_node; inp_locally_locked = 0; dir = PFIL_DIR(flags); ss = DPCPU_PTR(ss); /* * m_pullup is not required here because ip6_{input|output} * already do the heavy lifting for us. */ ip6 = mtod(*m, struct ip6_hdr *); /* * Only continue processing if the packet is TCP * XXX: We should follow the next header fields * as shown on Pg 6 RFC 2460, but right now we'll * only check pkts that have no extension headers. */ if (ip6->ip6_nxt != IPPROTO_TCP) goto ret6; /* * Create a tcphdr struct starting at the correct offset * in the ipv6 packet. */ ip6_hl = sizeof(struct ip6_hdr); th = (struct tcphdr *)((caddr_t)ip6 + ip6_hl); /* * Only pkts selected by the tcp port filter * can be inserted into the pkt_queue */ if ((siftr_port_filter != 0) && (siftr_port_filter != ntohs(th->th_sport)) && (siftr_port_filter != ntohs(th->th_dport))) { goto ret6; } /* * If a kernel subsystem reinjects packets into the stack, our pfil * hook will be called multiple times for the same packet. * Make sure we only process unique packets. */ if (siftr_chkreinject(*m, dir, ss)) goto ret6; if (dir == PFIL_IN) ss->n_in++; else ss->n_out++; /* * For inbound packets, the pfil hooks don't provide a pointer to the * inpcb, so we need to find it ourselves and lock it. */ if (!inp) { /* Find the corresponding inpcb for this pkt. */ inp = siftr_findinpcb(INP_IPV6, (struct ip *)ip6, *m, th->th_sport, th->th_dport, dir, ss); if (inp == NULL) goto ret6; else inp_locally_locked = 1; } /* Find the TCP control block that corresponds with this packet. */ tp = intotcpcb(inp); /* * If we can't find the TCP control block (happens occasionaly for a * packet sent during the shutdown phase of a TCP connection), or the * TCP control block has not initialized (happens during TCPS_SYN_SENT), * bail. */ if (tp == NULL || tp->t_state < TCPS_ESTABLISHED) { if (dir == PFIL_IN) ss->nskip_in_tcpcb++; else ss->nskip_out_tcpcb++; goto inp_unlock6; } hash_id = siftr_get_flowid(inp, INP_IPV6, &hash_type); counter_list = counter_hash + (hash_id & siftr_hashmask); hash_node = siftr_find_flow(counter_list, hash_id); /* If this flow hasn't been seen before, we create a new entry. */ if (!hash_node) { struct flow_info info; ip6_sprintf(info.laddr, &inp->in6p_laddr); ip6_sprintf(info.faddr, &inp->in6p_faddr); info.lport = ntohs(inp->inp_lport); info.fport = ntohs(inp->inp_fport); info.key = hash_id; info.ipver = INP_IPV6; hash_node = siftr_new_hash_node(info, dir, ss); } if (!hash_node) { goto inp_unlock6; } pn = malloc(sizeof(struct pkt_node), M_SIFTR_PKTNODE, M_NOWAIT|M_ZERO); if (pn == NULL) { if (dir == PFIL_IN) ss->nskip_in_malloc++; else ss->nskip_out_malloc++; goto inp_unlock6; } pn->flowid = hash_id; pn->flowtype = hash_type; siftr_siftdata(pn, inp, tp, INP_IPV6, dir, inp_locally_locked); - /* XXX: Figure out how to generate hashes for IPv6 packets. */ - mtx_lock(&siftr_pkt_queue_mtx); STAILQ_INSERT_TAIL(&pkt_queue, pn, nodes); mtx_unlock(&siftr_pkt_queue_mtx); goto ret6; inp_unlock6: if (inp_locally_locked) INP_RUNLOCK(inp); ret6: return (PFIL_PASS); } #endif /* #ifdef SIFTR_IPV6 */ VNET_DEFINE_STATIC(pfil_hook_t, siftr_inet_hook); #define V_siftr_inet_hook VNET(siftr_inet_hook) #ifdef SIFTR_IPV6 VNET_DEFINE_STATIC(pfil_hook_t, siftr_inet6_hook); #define V_siftr_inet6_hook VNET(siftr_inet6_hook) #endif static int siftr_pfil(int action) { struct pfil_hook_args pha = { .pa_version = PFIL_VERSION, .pa_flags = PFIL_IN | PFIL_OUT, .pa_modname = "siftr", .pa_rulname = "default", }; struct pfil_link_args pla = { .pa_version = PFIL_VERSION, .pa_flags = PFIL_IN | PFIL_OUT | PFIL_HEADPTR | PFIL_HOOKPTR, }; VNET_ITERATOR_DECL(vnet_iter); VNET_LIST_RLOCK(); VNET_FOREACH(vnet_iter) { CURVNET_SET(vnet_iter); if (action == HOOK) { pha.pa_mbuf_chk = siftr_chkpkt; pha.pa_type = PFIL_TYPE_IP4; V_siftr_inet_hook = pfil_add_hook(&pha); pla.pa_hook = V_siftr_inet_hook; pla.pa_head = V_inet_pfil_head; (void)pfil_link(&pla); #ifdef SIFTR_IPV6 pha.pa_mbuf_chk = siftr_chkpkt6; pha.pa_type = PFIL_TYPE_IP6; V_siftr_inet6_hook = pfil_add_hook(&pha); pla.pa_hook = V_siftr_inet6_hook; pla.pa_head = V_inet6_pfil_head; (void)pfil_link(&pla); #endif } else if (action == UNHOOK) { pfil_remove_hook(V_siftr_inet_hook); #ifdef SIFTR_IPV6 pfil_remove_hook(V_siftr_inet6_hook); #endif } CURVNET_RESTORE(); } VNET_LIST_RUNLOCK(); return (0); } static int siftr_sysctl_logfile_name_handler(SYSCTL_HANDLER_ARGS) { struct alq *new_alq; int error; error = sysctl_handle_string(oidp, arg1, arg2, req); /* Check for error or same filename */ if (error != 0 || req->newptr == NULL || strncmp(siftr_logfile, arg1, arg2) == 0) goto done; - /* Filname changed */ + /* file name changed */ error = alq_open(&new_alq, arg1, curthread->td_ucred, SIFTR_LOG_FILE_MODE, SIFTR_ALQ_BUFLEN, 0); if (error != 0) goto done; /* * If disabled, siftr_alq == NULL so we simply close * the alq as we've proved it can be opened. * If enabled, close the existing alq and switch the old * for the new. */ if (siftr_alq == NULL) { alq_close(new_alq); } else { alq_close(siftr_alq); siftr_alq = new_alq; } /* Update filename upon success */ strlcpy(siftr_logfile, arg1, arg2); done: return (error); } static int siftr_manage_ops(uint8_t action) { struct siftr_stats totalss; struct timeval tval; struct flow_hash_node *counter, *tmp_counter; struct sbuf *s; int i, error; uint32_t bytes_to_write, total_skipped_pkts; error = 0; total_skipped_pkts = 0; /* Init an autosizing sbuf that initially holds 200 chars. */ if ((s = sbuf_new(NULL, NULL, 200, SBUF_AUTOEXTEND)) == NULL) return (-1); if (action == SIFTR_ENABLE && siftr_pkt_manager_thr == NULL) { /* * Create our alq * XXX: We should abort if alq_open fails! */ alq_open(&siftr_alq, siftr_logfile, curthread->td_ucred, SIFTR_LOG_FILE_MODE, SIFTR_ALQ_BUFLEN, 0); STAILQ_INIT(&pkt_queue); DPCPU_ZERO(ss); siftr_exit_pkt_manager_thread = 0; kthread_add(&siftr_pkt_manager_thread, NULL, NULL, &siftr_pkt_manager_thr, RFNOWAIT, 0, "siftr_pkt_manager_thr"); siftr_pfil(HOOK); microtime(&tval); sbuf_printf(s, "enable_time_secs=%jd\tenable_time_usecs=%06ld\t" "siftrver=%s\tsysname=%s\tsysver=%u\tipmode=%u\n", (intmax_t)tval.tv_sec, tval.tv_usec, MODVERSION_STR, SYS_NAME, __FreeBSD_version, SIFTR_IPMODE); sbuf_finish(s); alq_writen(siftr_alq, sbuf_data(s), sbuf_len(s), ALQ_WAITOK); } else if (action == SIFTR_DISABLE && siftr_pkt_manager_thr != NULL) { /* * Remove the pfil hook functions. All threads currently in * the hook functions are allowed to exit before siftr_pfil() * returns. */ siftr_pfil(UNHOOK); /* This will block until the pkt manager thread unlocks it. */ mtx_lock(&siftr_pkt_mgr_mtx); /* Tell the pkt manager thread that it should exit now. */ siftr_exit_pkt_manager_thread = 1; /* * Wake the pkt_manager thread so it realises that * siftr_exit_pkt_manager_thread == 1 and exits gracefully. * The wakeup won't be delivered until we unlock * siftr_pkt_mgr_mtx so this isn't racy. */ wakeup(&wait_for_pkt); /* Wait for the pkt_manager thread to exit. */ mtx_sleep(siftr_pkt_manager_thr, &siftr_pkt_mgr_mtx, PWAIT, "thrwait", 0); siftr_pkt_manager_thr = NULL; mtx_unlock(&siftr_pkt_mgr_mtx); totalss.n_in = DPCPU_VARSUM(ss, n_in); totalss.n_out = DPCPU_VARSUM(ss, n_out); totalss.nskip_in_malloc = DPCPU_VARSUM(ss, nskip_in_malloc); totalss.nskip_out_malloc = DPCPU_VARSUM(ss, nskip_out_malloc); totalss.nskip_in_tcpcb = DPCPU_VARSUM(ss, nskip_in_tcpcb); totalss.nskip_out_tcpcb = DPCPU_VARSUM(ss, nskip_out_tcpcb); totalss.nskip_in_inpcb = DPCPU_VARSUM(ss, nskip_in_inpcb); totalss.nskip_out_inpcb = DPCPU_VARSUM(ss, nskip_out_inpcb); total_skipped_pkts = totalss.nskip_in_malloc + totalss.nskip_out_malloc + totalss.nskip_in_tcpcb + totalss.nskip_out_tcpcb + totalss.nskip_in_inpcb + totalss.nskip_out_inpcb; microtime(&tval); sbuf_printf(s, "disable_time_secs=%jd\tdisable_time_usecs=%06ld\t" "num_inbound_tcp_pkts=%ju\tnum_outbound_tcp_pkts=%ju\t" "total_tcp_pkts=%ju\tnum_inbound_skipped_pkts_malloc=%u\t" "num_outbound_skipped_pkts_malloc=%u\t" "num_inbound_skipped_pkts_tcpcb=%u\t" "num_outbound_skipped_pkts_tcpcb=%u\t" "num_inbound_skipped_pkts_inpcb=%u\t" "num_outbound_skipped_pkts_inpcb=%u\t" "total_skipped_tcp_pkts=%u\tflow_list=", (intmax_t)tval.tv_sec, tval.tv_usec, (uintmax_t)totalss.n_in, (uintmax_t)totalss.n_out, (uintmax_t)(totalss.n_in + totalss.n_out), totalss.nskip_in_malloc, totalss.nskip_out_malloc, totalss.nskip_in_tcpcb, totalss.nskip_out_tcpcb, totalss.nskip_in_inpcb, totalss.nskip_out_inpcb, total_skipped_pkts); /* * Iterate over the flow hash, printing a summary of each * flow seen and freeing any malloc'd memory. * The hash consists of an array of LISTs (man 3 queue). */ for (i = 0; i <= siftr_hashmask; i++) { LIST_FOREACH_SAFE(counter, counter_hash + i, nodes, tmp_counter) { sbuf_printf(s, "%s;%hu-%s;%hu,", counter->const_info.laddr, counter->const_info.lport, counter->const_info.faddr, counter->const_info.fport); free(counter, M_SIFTR_HASHNODE); } LIST_INIT(counter_hash + i); } sbuf_printf(s, "\n"); sbuf_finish(s); i = 0; do { bytes_to_write = min(SIFTR_ALQ_BUFLEN, sbuf_len(s)-i); alq_writen(siftr_alq, sbuf_data(s)+i, bytes_to_write, ALQ_WAITOK); i += bytes_to_write; } while (i < sbuf_len(s)); alq_close(siftr_alq); siftr_alq = NULL; } else error = EINVAL; sbuf_delete(s); /* * XXX: Should be using ret to check if any functions fail * and set error appropriately */ return (error); } static int siftr_sysctl_enabled_handler(SYSCTL_HANDLER_ARGS) { int error; uint32_t new; new = siftr_enabled; error = sysctl_handle_int(oidp, &new, 0, req); if (error == 0 && req->newptr != NULL) { if (new > 1) return (EINVAL); else if (new != siftr_enabled) { if ((error = siftr_manage_ops(new)) == 0) { siftr_enabled = new; } else { siftr_manage_ops(SIFTR_DISABLE); } } } return (error); } static void siftr_shutdown_handler(void *arg) { if (siftr_enabled == 1) { siftr_manage_ops(SIFTR_DISABLE); } } /* * Module is being unloaded or machine is shutting down. Take care of cleanup. */ static int deinit_siftr(void) { /* Cleanup. */ siftr_manage_ops(SIFTR_DISABLE); hashdestroy(counter_hash, M_SIFTR, siftr_hashmask); mtx_destroy(&siftr_pkt_queue_mtx); mtx_destroy(&siftr_pkt_mgr_mtx); return (0); } /* * Module has just been loaded into the kernel. */ static int init_siftr(void) { EVENTHANDLER_REGISTER(shutdown_pre_sync, siftr_shutdown_handler, NULL, SHUTDOWN_PRI_FIRST); /* Initialise our flow counter hash table. */ counter_hash = hashinit(SIFTR_EXPECTED_MAX_TCP_FLOWS, M_SIFTR, &siftr_hashmask); mtx_init(&siftr_pkt_queue_mtx, "siftr_pkt_queue_mtx", NULL, MTX_DEF); mtx_init(&siftr_pkt_mgr_mtx, "siftr_pkt_mgr_mtx", NULL, MTX_DEF); /* Print message to the user's current terminal. */ uprintf("\nStatistical Information For TCP Research (SIFTR) %s\n" " http://caia.swin.edu.au/urp/newtcp\n\n", MODVERSION_STR); return (0); } /* * This is the function that is called to load and unload the module. * When the module is loaded, this function is called once with * "what" == MOD_LOAD * When the module is unloaded, this function is called twice with * "what" = MOD_QUIESCE first, followed by "what" = MOD_UNLOAD second * When the system is shut down e.g. CTRL-ALT-DEL or using the shutdown command, * this function is called once with "what" = MOD_SHUTDOWN * When the system is shut down, the handler isn't called until the very end * of the shutdown sequence i.e. after the disks have been synced. */ static int siftr_load_handler(module_t mod, int what, void *arg) { int ret; switch (what) { case MOD_LOAD: ret = init_siftr(); break; case MOD_QUIESCE: case MOD_SHUTDOWN: ret = deinit_siftr(); break; case MOD_UNLOAD: ret = 0; break; default: ret = EINVAL; break; } return (ret); } static moduledata_t siftr_mod = { .name = "siftr", .evhand = siftr_load_handler, }; /* * Param 1: name of the kernel module * Param 2: moduledata_t struct containing info about the kernel module * and the execution entry point for the module * Param 3: From sysinit_sub_id enumeration in /usr/include/sys/kernel.h * Defines the module initialisation order * Param 4: From sysinit_elem_order enumeration in /usr/include/sys/kernel.h * Defines the initialisation order of this kld relative to others * within the same subsystem as defined by param 3 */ DECLARE_MODULE(siftr, siftr_mod, SI_SUB_LAST, SI_ORDER_ANY); MODULE_DEPEND(siftr, alq, 1, 1, 1); MODULE_VERSION(siftr, MODVERSION);