2006-06-06 04:30:32 +04:00
/*
* tcpprobe - Observe the TCP flow with kprobes .
*
* The idea for this came from Werner Almesberger ' s umlsim
* Copyright ( C ) 2004 , Stephen Hemminger < shemminger @ osdl . org >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
2007-07-12 06:43:52 +04:00
* the Free Software Foundation ; either version 2 of the License .
2006-06-06 04:30:32 +04:00
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include <linux/kernel.h>
# include <linux/kprobes.h>
# include <linux/socket.h>
# include <linux/tcp.h>
# include <linux/proc_fs.h>
# include <linux/module.h>
2007-03-25 08:35:33 +04:00
# include <linux/ktime.h>
# include <linux/time.h>
2007-09-12 14:01:34 +04:00
# include <net/net_namespace.h>
2006-06-06 04:30:32 +04:00
# include <net/tcp.h>
2007-01-23 22:38:57 +03:00
MODULE_AUTHOR ( " Stephen Hemminger <shemminger@linux-foundation.org> " ) ;
2006-06-06 04:30:32 +04:00
MODULE_DESCRIPTION ( " TCP cwnd snooper " ) ;
MODULE_LICENSE ( " GPL " ) ;
2007-07-12 06:43:52 +04:00
MODULE_VERSION ( " 1.1 " ) ;
2006-06-06 04:30:32 +04:00
2007-03-25 08:35:33 +04:00
static int port __read_mostly = 0 ;
2006-06-06 04:30:32 +04:00
MODULE_PARM_DESC ( port , " Port to match (0=all) " ) ;
module_param ( port , int , 0 ) ;
2007-07-12 06:43:52 +04:00
static int bufsize __read_mostly = 4096 ;
MODULE_PARM_DESC ( bufsize , " Log buffer size in packets (4096) " ) ;
2006-06-06 04:30:32 +04:00
module_param ( bufsize , int , 0 ) ;
2007-03-25 08:35:33 +04:00
static int full __read_mostly ;
MODULE_PARM_DESC ( full , " Full log (1=every ack packet received, 0=only cwnd changes) " ) ;
module_param ( full , int , 0 ) ;
2006-06-06 04:30:32 +04:00
static const char procname [ ] = " tcpprobe " ;
2007-07-12 06:43:52 +04:00
struct tcp_log {
ktime_t tstamp ;
__be32 saddr , daddr ;
__be16 sport , dport ;
u16 length ;
u32 snd_nxt ;
u32 snd_una ;
u32 snd_wnd ;
u32 snd_cwnd ;
u32 ssthresh ;
u32 srtt ;
} ;
static struct {
2007-03-25 08:35:33 +04:00
spinlock_t lock ;
2006-06-06 04:30:32 +04:00
wait_queue_head_t wait ;
2007-03-25 08:35:33 +04:00
ktime_t start ;
u32 lastcwnd ;
2006-06-06 04:30:32 +04:00
2007-07-12 06:43:52 +04:00
unsigned long head , tail ;
struct tcp_log * log ;
} tcp_probe ;
2007-06-05 11:19:24 +04:00
2007-07-12 06:43:52 +04:00
static inline int tcp_probe_used ( void )
2006-06-06 04:30:32 +04:00
{
2007-07-12 06:43:52 +04:00
return ( tcp_probe . head - tcp_probe . tail ) % bufsize ;
}
static inline int tcp_probe_avail ( void )
{
return bufsize - tcp_probe_used ( ) ;
2007-06-05 11:19:24 +04:00
}
2006-06-06 04:30:32 +04:00
2007-03-25 08:35:33 +04:00
/*
* Hook inserted to be called before each receive packet .
* Note : arguments must match tcp_rcv_established ( ) !
*/
static int jtcp_rcv_established ( struct sock * sk , struct sk_buff * skb ,
struct tcphdr * th , unsigned len )
2006-06-06 04:30:32 +04:00
{
const struct tcp_sock * tp = tcp_sk ( sk ) ;
const struct inet_sock * inet = inet_sk ( sk ) ;
2007-03-25 08:35:33 +04:00
/* Only update if port matches */
if ( ( port = = 0 | | ntohs ( inet - > dport ) = = port | | ntohs ( inet - > sport ) = = port )
2007-07-12 06:43:52 +04:00
& & ( full | | tp - > snd_cwnd ! = tcp_probe . lastcwnd ) ) {
spin_lock ( & tcp_probe . lock ) ;
/* If log fills, just silently drop */
if ( tcp_probe_avail ( ) > 1 ) {
struct tcp_log * p = tcp_probe . log + tcp_probe . head ;
p - > tstamp = ktime_get ( ) ;
p - > saddr = inet - > saddr ;
p - > sport = inet - > sport ;
p - > daddr = inet - > daddr ;
p - > dport = inet - > dport ;
p - > length = skb - > len ;
p - > snd_nxt = tp - > snd_nxt ;
p - > snd_una = tp - > snd_una ;
p - > snd_cwnd = tp - > snd_cwnd ;
p - > snd_wnd = tp - > snd_wnd ;
2007-07-15 05:57:19 +04:00
p - > ssthresh = tcp_current_ssthresh ( sk ) ;
2007-07-12 06:43:52 +04:00
p - > srtt = tp - > srtt > > 3 ;
tcp_probe . head = ( tcp_probe . head + 1 ) % bufsize ;
}
tcp_probe . lastcwnd = tp - > snd_cwnd ;
spin_unlock ( & tcp_probe . lock ) ;
wake_up ( & tcp_probe . wait ) ;
2006-06-06 04:30:32 +04:00
}
jprobe_return ( ) ;
return 0 ;
}
2007-07-12 06:43:52 +04:00
static struct jprobe tcp_jprobe = {
2006-10-02 13:17:30 +04:00
. kp = {
2007-03-25 08:35:33 +04:00
. symbol_name = " tcp_rcv_established " ,
2006-10-02 13:17:30 +04:00
} ,
2007-07-19 12:48:10 +04:00
. entry = jtcp_rcv_established ,
2006-06-06 04:30:32 +04:00
} ;
static int tcpprobe_open ( struct inode * inode , struct file * file )
{
2007-07-12 06:43:52 +04:00
/* Reset (empty) log */
spin_lock_bh ( & tcp_probe . lock ) ;
tcp_probe . head = tcp_probe . tail = 0 ;
tcp_probe . start = ktime_get ( ) ;
spin_unlock_bh ( & tcp_probe . lock ) ;
2006-06-06 04:30:32 +04:00
return 0 ;
}
2007-07-12 06:43:52 +04:00
static int tcpprobe_sprint ( char * tbuf , int n )
{
const struct tcp_log * p
= tcp_probe . log + tcp_probe . tail % bufsize ;
struct timespec tv
= ktime_to_timespec ( ktime_sub ( p - > tstamp , tcp_probe . start ) ) ;
return snprintf ( tbuf , n ,
2008-10-31 10:53:57 +03:00
" %lu.%09lu %pI4:%u %pI4:%u %d %#x %#x %u %u %u %u \n " ,
2007-07-12 06:43:52 +04:00
( unsigned long ) tv . tv_sec ,
( unsigned long ) tv . tv_nsec ,
2008-10-31 10:53:57 +03:00
& p - > saddr , ntohs ( p - > sport ) ,
& p - > daddr , ntohs ( p - > dport ) ,
2007-07-12 06:43:52 +04:00
p - > length , p - > snd_nxt , p - > snd_una ,
p - > snd_cwnd , p - > ssthresh , p - > snd_wnd , p - > srtt ) ;
}
2006-06-06 04:30:32 +04:00
static ssize_t tcpprobe_read ( struct file * file , char __user * buf ,
size_t len , loff_t * ppos )
{
2006-07-31 07:21:45 +04:00
int error = 0 , cnt = 0 ;
2006-06-06 04:30:32 +04:00
if ( ! buf | | len < 0 )
return - EINVAL ;
2007-07-12 06:43:52 +04:00
while ( cnt < len ) {
char tbuf [ 128 ] ;
int width ;
/* Wait for data in buffer */
error = wait_event_interruptible ( tcp_probe . wait ,
tcp_probe_used ( ) > 0 ) ;
if ( error )
break ;
2006-06-06 04:30:32 +04:00
2007-07-12 06:43:52 +04:00
spin_lock_bh ( & tcp_probe . lock ) ;
if ( tcp_probe . head = = tcp_probe . tail ) {
/* multiple readers race? */
spin_unlock_bh ( & tcp_probe . lock ) ;
continue ;
}
2006-06-06 04:30:32 +04:00
2007-07-12 06:43:52 +04:00
width = tcpprobe_sprint ( tbuf , sizeof ( tbuf ) ) ;
2006-06-06 04:30:32 +04:00
2008-04-25 08:11:58 +04:00
if ( cnt + width < len )
2007-07-12 06:43:52 +04:00
tcp_probe . tail = ( tcp_probe . tail + 1 ) % bufsize ;
2006-06-06 04:30:32 +04:00
2007-07-12 06:43:52 +04:00
spin_unlock_bh ( & tcp_probe . lock ) ;
/* if record greater than space available
return partial buffer ( so far ) */
2008-04-25 08:11:58 +04:00
if ( cnt + width > = len )
2007-07-12 06:43:52 +04:00
break ;
2008-04-25 08:11:58 +04:00
if ( copy_to_user ( buf + cnt , tbuf , width ) )
return - EFAULT ;
2007-07-12 06:43:52 +04:00
cnt + = width ;
}
2006-06-06 04:30:32 +04:00
2007-07-12 06:43:52 +04:00
return cnt = = 0 ? error : cnt ;
2006-06-06 04:30:32 +04:00
}
2007-02-12 11:55:35 +03:00
static const struct file_operations tcpprobe_fops = {
2006-06-06 04:30:32 +04:00
. owner = THIS_MODULE ,
. open = tcpprobe_open ,
. read = tcpprobe_read ,
} ;
static __init int tcpprobe_init ( void )
{
int ret = - ENOMEM ;
2007-07-12 06:43:52 +04:00
init_waitqueue_head ( & tcp_probe . wait ) ;
spin_lock_init ( & tcp_probe . lock ) ;
if ( bufsize < 0 )
return - EINVAL ;
2008-07-11 03:51:32 +04:00
tcp_probe . log = kcalloc ( bufsize , sizeof ( struct tcp_log ) , GFP_KERNEL ) ;
2007-07-12 06:43:52 +04:00
if ( ! tcp_probe . log )
goto err0 ;
2006-06-06 04:30:32 +04:00
2007-09-12 14:01:34 +04:00
if ( ! proc_net_fops_create ( & init_net , procname , S_IRUSR , & tcpprobe_fops ) )
2006-06-06 04:30:32 +04:00
goto err0 ;
2007-07-12 06:43:52 +04:00
ret = register_jprobe ( & tcp_jprobe ) ;
2006-06-06 04:30:32 +04:00
if ( ret )
goto err1 ;
2007-07-12 06:43:52 +04:00
pr_info ( " TCP probe registered (port=%d) \n " , port ) ;
2006-06-06 04:30:32 +04:00
return 0 ;
err1 :
2007-09-12 14:01:34 +04:00
proc_net_remove ( & init_net , procname ) ;
2006-06-06 04:30:32 +04:00
err0 :
2007-07-12 06:43:52 +04:00
kfree ( tcp_probe . log ) ;
2006-06-06 04:30:32 +04:00
return ret ;
}
module_init ( tcpprobe_init ) ;
static __exit void tcpprobe_exit ( void )
{
2007-09-12 14:01:34 +04:00
proc_net_remove ( & init_net , procname ) ;
2007-07-12 06:43:52 +04:00
unregister_jprobe ( & tcp_jprobe ) ;
kfree ( tcp_probe . log ) ;
2006-06-06 04:30:32 +04:00
}
module_exit ( tcpprobe_exit ) ;