#define KSPEEDTEST_VERSION "2.0.0 2014-20-03" /*------------------------------------------------------------------------------- * * Copyright (C) 2006-2014 AVM GmbH * * 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 * the Free Software Foundation version 2 of the License. * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *------------------------------------------------------------------------------- * *------------------------------------------------------------------------------- * kspeedtest: AVM Kernel Speedtest *------------------------------------------------------------------------------- * * After module load kspeedtest can be controlled via proc interface * /proc/net/kspeedtest * * echo > /proc/net/kspeedtest/control * * Commands: * * - start tcp [port] - Start TCP server (default Port 4711) * - start udp [port] - Start UDP server (default Port 4711) * - start speedtest [port] [download_duration (seconds)] [upload_duration (seconds)] * - Starts a complete speed-test procedure * (defaults: Port 4712, downstream duration 20 (sec), upstream duration 10 (sec)) * - stop tcp - Stop TCP server * - stop udp - Stop UDP server * - stop speedtest - Stops the speed-test procedure (just to interrupt, regularly a speed-test procedure finishes automatically). * - stop all - Stop all servers * - verbose - Print stats periodically as kernel message * - noverbose - Turn off verbose * - bidirect [port] - Bidirect UDP traffic (default Port 4712) * - nobidirect - Stop bidirectional traffic * - reset - Reset stats * * * Show stats (Received Bytes, BPS, AVG BPS): * * cat /proc/net/kspeedtest/stats * * Show connections * * cat /proc/net/kspeedtest/connections * * vim: expandtab fileencoding=utf-8 * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_TI_PACKET_PROCESSOR #include #endif #define DEFAULT_UDP_PORT 4711 #define DEFAULT_UDP_BIDIRECT_PORT 4712 #define DEFAULT_SPEEDTEST_PORT 4712 #define DEFAULT_SPEEDTEST_DURATION_DOWN 20 #define DEFAULT_SPEEDTEST_DURATION_UP 10 #define MAXIMUM_SPEEDTEST_DURATION 120 #define MAXIMUM_TCP_PARALLEL_CONNECTIONS 8 #define DEFAULT_TCP_PORT 4711 #define MODULE_NAME "kspeedtest" #undef NO_AUTO_RWIN const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; MODULE_LICENSE("GPL"); static DEFINE_MUTEX(kspeedtest_mutex); static struct timer_list ktimer; static void Second(unsigned long param); static struct kspeedtest { int secondtimer; int verbose; int null_vpid; unsigned int sleepdelay; unsigned udp_port; unsigned udp_bidirect_port; unsigned tcp_port; unsigned int seconds; unsigned int packets; unsigned long total_packets; unsigned long bytes; unsigned long long total_bytes; unsigned int current_bps; unsigned int current_pps; unsigned int avg_bps; unsigned int avg_pps; unsigned int udp_received; unsigned int udp_bidirect_received; unsigned long last_udp_timestamp; unsigned long last_udp_bidirect_timestamp; unsigned test_port; unsigned int test_duration_download; unsigned int test_duration_upload; int test_calculation_seconds; char test_state[20]; unsigned long test_kbits_avg; unsigned long test_kbits_min; unsigned long test_kbits_max; } kspeedtest; enum client_state { client_init, client_listen, client_connected }; #define MAX_CLIENTS 48 struct socket_list { struct socket_list *next; struct socket *sock; }; struct kthread_t { struct task_struct *thread; struct task_struct *worker; struct socket *sock; struct socket_list *clients; int current_client; enum client_state client_state; struct sockaddr_in6 addr; int running; }; struct kthread_t *udp_thread = NULL; struct kthread_t *udp_bidirect_thread = NULL; struct kthread_t *tcp_thread = NULL; struct kthread_t *speedtest_thread = NULL; /* function prototypes */ static int udp_receive(struct socket *sock, struct sockaddr_in6 *addr, unsigned char *buf, int len, int bidirect); static int udp_send(struct socket *sock, struct sockaddr_in6 *addr, unsigned char *buf, int len); static void __init kspeedtest_proc_init(void); static void __init kspeedtest_proc_exit(void); typedef int my_fprintf(void *, const char *, ...) #ifdef __GNUC__ __attribute__ ((__format__(__printf__, 2, 3))) #endif ; static void reset_stats(void) { kspeedtest.seconds = 0; kspeedtest.packets = 0; kspeedtest.total_packets = 0; kspeedtest.bytes = 0; kspeedtest.total_bytes = 0; kspeedtest.current_bps = 0; kspeedtest.current_pps = 0; kspeedtest.avg_bps = 0; kspeedtest.avg_pps = 0; kspeedtest.test_kbits_avg = 0; kspeedtest.test_kbits_max = 0; kspeedtest.test_kbits_min = 0; } static void Second(unsigned long param) { u64 x64; // Stats of Null VPID #ifdef CONFIG_TI_PACKET_PROCESSOR if (kspeedtest.null_vpid != -1) { TI_PP_VPID_STATS vstats; ti_ppm_get_vpid_stats(kspeedtest.null_vpid, &vstats); ti_ppm_clear_vpid_stats(kspeedtest.null_vpid); kspeedtest.bytes += vstats.tx_byte_lo; kspeedtest.packets += vstats.tx_unicast_pkt; if (vstats.tx_byte_lo > 0) { kspeedtest.udp_received = 1; kspeedtest.last_udp_timestamp = jiffies; } } #endif kspeedtest.seconds++; kspeedtest.total_bytes += kspeedtest.bytes; kspeedtest.total_packets += kspeedtest.packets; kspeedtest.current_bps = kspeedtest.bytes*8; x64 = kspeedtest.total_bytes*8; do_div(x64, kspeedtest.seconds); kspeedtest.avg_bps = x64; kspeedtest.current_pps = kspeedtest.packets; kspeedtest.avg_pps = kspeedtest.total_packets / kspeedtest.seconds; // Speedtest (kbit/s) calculation if (speedtest_thread && kspeedtest.test_calculation_seconds >= 0) { if (kspeedtest.test_calculation_seconds == 0) { reset_stats(); } else { // MIN / MAX Calculation x64 = kspeedtest.bytes; do_div(x64, 128); if (x64 < kspeedtest.test_kbits_min || kspeedtest.test_kbits_min == 0) { kspeedtest.test_kbits_min = x64; } if (x64 > kspeedtest.test_kbits_max || kspeedtest.test_kbits_max == 0) { kspeedtest.test_kbits_max = x64; } // AVG calculation x64 = kspeedtest.total_bytes; do_div(x64, (128 * kspeedtest.test_calculation_seconds)); kspeedtest.test_kbits_avg = x64; } kspeedtest.test_calculation_seconds++; } ktimer.expires = jiffies + HZ; add_timer(&ktimer); if (kspeedtest.verbose && kspeedtest.bytes) printk(KERN_ERR MODULE_NAME": Bytes %llu \t BPS %u AVG %u Packets %lu PPS %u AVG %u\n", kspeedtest.total_bytes, kspeedtest.current_bps, kspeedtest.avg_bps, kspeedtest.total_packets, kspeedtest.current_pps, kspeedtest.avg_pps); if (kspeedtest.verbose && tcp_thread) { struct socket_list *sl = tcp_thread->clients; int count = 0; for (; sl; sl = sl->next) count++; printk(KERN_ERR "nr clients %d \n", count); } kspeedtest.bytes = 0; kspeedtest.packets = 0; if (jiffies > kspeedtest.last_udp_timestamp + HZ) kspeedtest.udp_received = 0; if (jiffies > kspeedtest.last_udp_bidirect_timestamp + HZ) kspeedtest.udp_bidirect_received = 0; } static void kspeedtest_show_stats(my_fprintf fprintffunc, void *arg) { if (kspeedtest.test_kbits_avg) { (*fprintffunc)(arg, "Downstream (kbit/s)\tAVG %lu\tMIN %lu\tMAX %lu\n", kspeedtest.test_kbits_avg, kspeedtest.test_kbits_min, kspeedtest.test_kbits_max); } else { (*fprintffunc)(arg, "Bytes %llu \t BPS %u \t AVG %u Packets %lu PPS %u AVG %u\n", kspeedtest.total_bytes, kspeedtest.current_bps, kspeedtest.avg_bps, kspeedtest.total_packets, kspeedtest.current_pps, kspeedtest.avg_pps); } } /***************************************************************************/ /* UDP */ /***************************************************************************/ static int kspeedtest_udp_thread(void) { int size, err; int bufsize = 1500; unsigned char buf[bufsize+1]; udp_thread->running = 1; current->flags |= PF_NOFREEZE; daemonize(MODULE_NAME); allow_signal(SIGKILL); /* create a udp socket */ if ((err = sock_create_kern(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, &udp_thread->sock)) < 0) { printk(KERN_ERR MODULE_NAME": Could not create a datagram socket, error = %d\n", -ENXIO); goto out; } memset(&udp_thread->addr, 0, sizeof(struct sockaddr)); udp_thread->addr.sin6_family = AF_INET6; udp_thread->addr.sin6_addr = in6addr_any; udp_thread->addr.sin6_port = htons(kspeedtest.udp_port ? kspeedtest.udp_port : DEFAULT_UDP_PORT); if ( (err = udp_thread->sock->ops->bind(udp_thread->sock, (struct sockaddr *)&udp_thread->addr, sizeof(struct sockaddr_in6) ) ) < 0) { printk(KERN_ERR MODULE_NAME": Could not bind or connect to udp socket, error = %d\n", -err); goto close_and_out; } printk(KERN_INFO MODULE_NAME": listening on port %d\n", ntohs(udp_thread->addr.sin6_port)); memset(&buf, 0, bufsize+1); for (;;) { if (signal_pending(current)) break; size = udp_receive(udp_thread->sock, &udp_thread->addr, buf, bufsize, 0); if (size < 0) { printk(KERN_ERR MODULE_NAME": sock_recvmsg error = %d\n", size); } else if (size > 0) { kspeedtest.udp_received = 1; kspeedtest.last_udp_timestamp = jiffies; kspeedtest.bytes += size; kspeedtest.packets++; if (kspeedtest.verbose > 1) printk(KERN_ERR "udp receive: got %d bytes\n", size); } if (size > 0) udelay(kspeedtest.sleepdelay ? kspeedtest.sleepdelay : 10); else msleep(10); } close_and_out: sock_release(udp_thread->sock); udp_thread->sock = NULL; out: udp_thread->thread = NULL; udp_thread->running = 0; #ifdef CONFIG_TI_PACKET_PROCESSOR if (kspeedtest.null_vpid != -1) ti_ppm_delete_vpid(kspeedtest.null_vpid); kspeedtest.null_vpid = -1; #endif return 0; } static int kspeedtest_udp_bidirect_thread(void) { int size, err; int bufsize = 1500; unsigned char buf[bufsize+1]; struct sockaddr_in6 sendaddr; udp_bidirect_thread->running = 1; current->flags |= PF_NOFREEZE; daemonize(MODULE_NAME); allow_signal(SIGKILL); /* create a udp socket */ if ((err = sock_create_kern(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, &udp_bidirect_thread->sock)) < 0) { printk(KERN_ERR MODULE_NAME": Could not create a datagram socket, error = %d\n", -ENXIO); goto out; } memset(&udp_bidirect_thread->addr, 0, sizeof(struct sockaddr)); udp_bidirect_thread->addr.sin6_family = AF_INET6; udp_bidirect_thread->addr.sin6_addr = in6addr_any; udp_bidirect_thread->addr.sin6_port = htons(kspeedtest.udp_bidirect_port ? kspeedtest.udp_bidirect_port : DEFAULT_UDP_BIDIRECT_PORT); if ( (err = udp_bidirect_thread->sock->ops->bind(udp_bidirect_thread->sock, (struct sockaddr *)&udp_bidirect_thread->addr, sizeof(struct sockaddr_in6) ) ) < 0) { printk(KERN_ERR MODULE_NAME": Could not bind or connect to udp socket, error = %d\n", -err); goto close_and_out; } printk(KERN_INFO MODULE_NAME": listening on port %d\n", ntohs(udp_bidirect_thread->addr.sin6_port)); memset(&buf, 0, bufsize+1); for (;;) { if (signal_pending(current)) break; size = udp_receive(udp_bidirect_thread->sock, &udp_bidirect_thread->addr, buf, bufsize, 1); if (size < 0) { printk(KERN_ERR MODULE_NAME": sock_recvmsg error = %d\n", size); } else if (size > 0) { kspeedtest.bytes += size; kspeedtest.packets++; kspeedtest.udp_bidirect_received = 1; kspeedtest.last_udp_bidirect_timestamp = jiffies; if (kspeedtest.verbose > 1) printk(KERN_ERR "udp receive: got %d bytes\n", size); } sendaddr.sin6_addr = udp_bidirect_thread->addr.sin6_addr; sendaddr.sin6_family = AF_INET6; sendaddr.sin6_port = htons(kspeedtest.udp_bidirect_port ? kspeedtest.udp_bidirect_port : DEFAULT_UDP_BIDIRECT_PORT); size = udp_send(udp_bidirect_thread->sock, &sendaddr, buf, size); if (size < 0) printk(KERN_ERR MODULE_NAME": sock_sendmsg error = %d\n", size); else if (kspeedtest.verbose > 1) printk(KERN_ERR "udp_send: sent %d bytes\n", size); } close_and_out: sock_release(udp_bidirect_thread->sock); udp_bidirect_thread->sock = NULL; out: udp_bidirect_thread->thread = NULL; udp_bidirect_thread->running = 0; return 0; } static int kspeedtest_speedtest_thread(void) { int size, err; int bufsize = 1500; unsigned char buf[bufsize+1]; struct sockaddr_in6 sendaddr; unsigned long milliseconds = 0; unsigned long first_timestamp = 0; // Check test duration times. if (kspeedtest.test_duration_download > MAXIMUM_SPEEDTEST_DURATION || (kspeedtest.test_duration_download < 5 && kspeedtest.test_duration_download != 0)) { kspeedtest.test_duration_download = 0; if (kspeedtest.verbose) printk(KERN_INFO MODULE_NAME": Download duration was invalid. Disable downstream test.\n"); } if (kspeedtest.test_duration_upload > MAXIMUM_SPEEDTEST_DURATION || (kspeedtest.test_duration_upload < 5 && kspeedtest.test_duration_upload != 0)) { kspeedtest.test_duration_upload = 0; if (kspeedtest.verbose) printk(KERN_INFO MODULE_NAME": Upload duration was invalid. Disable upstream test.\n"); } if (kspeedtest.test_duration_download == 0 && kspeedtest.test_duration_upload == 0) { printk(KERN_ERR MODULE_NAME": Please specify an upload- or a download duration between 5 and %u seconds. Speedtest not started.\n", MAXIMUM_SPEEDTEST_DURATION); goto out; } kspeedtest.test_port = kspeedtest.test_port ? kspeedtest.test_port : DEFAULT_SPEEDTEST_PORT; speedtest_thread->running = 1; current->flags |= PF_NOFREEZE; daemonize(MODULE_NAME); allow_signal(SIGKILL); if ((err = sock_create_kern(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, &speedtest_thread->sock)) < 0) { printk(KERN_ERR MODULE_NAME": Could not create a datagram socket, error = %d\n", -ENXIO); goto out; } memset(&speedtest_thread->addr, 0, sizeof(struct sockaddr)); speedtest_thread->addr.sin6_family = AF_INET6; speedtest_thread->addr.sin6_addr = in6addr_any; speedtest_thread->addr.sin6_port = htons(kspeedtest.test_port); if ( (err = speedtest_thread->sock->ops->bind(speedtest_thread->sock, (struct sockaddr *)&speedtest_thread->addr, sizeof(struct sockaddr_in6) ) ) < 0) { printk(KERN_ERR MODULE_NAME": Could not bind or connect to udp socket, error = %d\n", -err); goto close_and_out; } printk(KERN_INFO MODULE_NAME": listening on port %d\n", ntohs(speedtest_thread->addr.sin6_port)); memset(&buf, 0, bufsize+1); // Receive address of measurement server (necessary for upstream test) sprintf(kspeedtest.test_state, "Ready"); if (kspeedtest.test_duration_upload > 0) { if (kspeedtest.verbose) printk(KERN_INFO MODULE_NAME": Wait for the first udp package to resolve the host address (upstream measurement).\n"); size = udp_receive(speedtest_thread->sock, &speedtest_thread->addr, buf, bufsize, 1); if (size < 0) { printk(KERN_ERR MODULE_NAME": sock_recvmsg error = %d\n", size); } else if (kspeedtest.verbose) { printk(KERN_INFO MODULE_NAME": Got udp package. Continue.\n"); } if (signal_pending(current)) goto close_and_out; } // Downstream test... if (kspeedtest.test_duration_download > 0) { sprintf(kspeedtest.test_state, "Downloading"); if (kspeedtest.verbose) printk(KERN_INFO MODULE_NAME": Starting downstream test (%u sec).\n", kspeedtest.test_duration_download); while (first_timestamp == 0 || (((kspeedtest.last_udp_timestamp-first_timestamp)/HZ) < kspeedtest.test_duration_download)) { if (signal_pending(current)) goto close_and_out; size = udp_receive(speedtest_thread->sock, &speedtest_thread->addr, buf, bufsize, 0); if (size < 0) { printk(KERN_ERR MODULE_NAME": sock_recvmsg error = %d\n", size); } else if (size > 0) { kspeedtest.udp_received = 1; kspeedtest.last_udp_timestamp = jiffies; kspeedtest.bytes += size; kspeedtest.packets++; if (kspeedtest.verbose > 1) printk(KERN_ERR "udp receive: got %d bytes\n", size); if (first_timestamp == 0) { first_timestamp = kspeedtest.last_udp_timestamp; if (kspeedtest.verbose) printk(KERN_INFO MODULE_NAME": Downstream test started.\n"); } // Start speed-test calculation in thread "Second"... if (kspeedtest.test_calculation_seconds < 0) { kspeedtest.test_calculation_seconds = 0; } udelay(kspeedtest.sleepdelay ? kspeedtest.sleepdelay : 10); } if (size <= 0) { msleep(10); } } kspeedtest.test_calculation_seconds = -1; if (kspeedtest.verbose) printk(KERN_INFO MODULE_NAME": Downstream(kbit/s) AVG %lu\tMAX %lu\tMIN %lu\n", kspeedtest.test_kbits_avg, kspeedtest.test_kbits_max, kspeedtest.test_kbits_min); } // Upstream test... if (kspeedtest.test_duration_upload > 0) { // Sleep some seconds - server has to be started on the client side after the client finishes the download test. sprintf(kspeedtest.test_state, "Waiting(4s)"); for (milliseconds = 0;milliseconds< 4000;milliseconds+=200) { if (signal_pending(current)) goto close_and_out; msleep(200); } sprintf(kspeedtest.test_state, "Uploading"); // Set the buffer size to maximum - with ip v6 support (no fragmentation, ipv4 is 1470). size = 1450; sendaddr.sin6_addr = speedtest_thread->addr.sin6_addr; sendaddr.sin6_family = AF_INET6; sendaddr.sin6_port = htons(kspeedtest.test_port); first_timestamp = 0; if (kspeedtest.verbose) printk(KERN_INFO MODULE_NAME": Starting upstream test (%u sec).\n", kspeedtest.test_duration_upload); while (first_timestamp == 0 || (((jiffies-first_timestamp)/HZ) < kspeedtest.test_duration_upload)) { if (signal_pending(current)) break; size = udp_send(speedtest_thread->sock, &sendaddr, buf, size); if (size < 0) printk(KERN_ERR MODULE_NAME": sock_sendmsg error = %d\n", size); else { if (kspeedtest.verbose > 1) printk(KERN_ERR "udp_send: sent %d bytes\n", size); if (first_timestamp == 0) { first_timestamp = jiffies; if (kspeedtest.verbose) printk(KERN_INFO MODULE_NAME": Upstream started.\n"); } } udelay(kspeedtest.sleepdelay ? kspeedtest.sleepdelay : 10); } if (kspeedtest.verbose) printk(KERN_INFO MODULE_NAME": Upstream test finished.\n"); } close_and_out: kspeedtest.test_calculation_seconds = -1; sock_release(speedtest_thread->sock); speedtest_thread->sock = NULL; out: speedtest_thread->thread = NULL; speedtest_thread->running = 0; return 0; } static int udp_send(struct socket *sock, struct sockaddr_in6 *addr, unsigned char *buf, int len) { struct msghdr msg; struct iovec iov; mm_segment_t oldfs; int size = 0; if (sock->sk==NULL) return 0; iov.iov_base = buf; iov.iov_len = len; msg.msg_flags = 0; msg.msg_name = addr; msg.msg_namelen = sizeof(struct sockaddr_in6); msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = NULL; oldfs = get_fs(); set_fs(KERNEL_DS); size = sock_sendmsg(sock,&msg,len); set_fs(oldfs); return size; } static int udp_receive(struct socket* sock, struct sockaddr_in6* addr, unsigned char* buf, int len, int bidirect) { int size = 0; if (bidirect) { struct msghdr msg; struct iovec iov; mm_segment_t oldfs; if (sock->sk==NULL) return 0; iov.iov_base = buf; iov.iov_len = len; msg.msg_flags = 0; msg.msg_name = addr; msg.msg_namelen = sizeof(struct sockaddr_in6); msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = NULL; oldfs = get_fs(); set_fs(KERNEL_DS); size = sock_recvmsg(sock,&msg,len,msg.msg_flags); set_fs(oldfs); /* * As we don't need to know the recv_addr we can speed up: * - peek in sk_receive_queue and just count bytes and packets * - setup null VPID for packet accelerators */ } else { struct sk_buff_head *rcvq = &sock->sk->sk_receive_queue; struct sk_buff *skb; spin_lock_bh(&rcvq->lock); while ((skb = skb_peek(rcvq)) != NULL) { #ifdef CONFIG_TI_PACKET_PROCESSOR // create null vpid #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) struct net_device* dev = __dev_get_by_index (&init_net,skb->skb_iif); if (kspeedtest.null_vpid == -1 && dev && dev->vpid_handle != -1) { kspeedtest.null_vpid = ti_ppm_create_vpid(&dev->vpid_block); if (kspeedtest.null_vpid < 0) printk(KERN_ERR "creating null vpid failed \n"); else { ti_ppm_set_vpid_flags( kspeedtest.null_vpid, TI_PP_VPID_FLG_RX_DISBL ); } } #else if (kspeedtest.null_vpid == -1 && skb->input_dev->vpid_handle != -1) { kspeedtest.null_vpid = ti_ppm_create_vpid(&skb->input_dev->vpid_block); if (kspeedtest.null_vpid < 0) printk(KERN_ERR "creating null vpid failed \n"); else { ti_ppm_set_vpid_flags( kspeedtest.null_vpid, TI_PP_VPID_FLG_RX_DISBL ); } } #endif if (kspeedtest.null_vpid != -1) { skb->pp_packet_info.ti_pp_flags |= TI_PPM_SESSION_ROUTED; ti_hil_null_hook(skb, kspeedtest.null_vpid); } #endif size += skb->len; __skb_unlink(skb, rcvq); kfree_skb(skb); } spin_unlock_bh(&rcvq->lock); } return size; } static int start_udp_thread(void) { if (udp_thread != NULL && udp_thread->thread != NULL) { printk(KERN_ERR MODULE_NAME": udp bidirect thread already started!\n"); return -1; } if (udp_thread) kfree(udp_thread); udp_thread = kmalloc(sizeof(struct kthread_t), GFP_KERNEL); if (!udp_thread) { printk(KERN_ERR MODULE_NAME": unable to allocate memory for udp_thread.\n"); return -ENOMEM; } memset(udp_thread, 0, sizeof(struct kthread_t)); udp_thread->thread = kthread_run((void *)kspeedtest_udp_thread, NULL, MODULE_NAME); if (IS_ERR(udp_thread->thread)) { printk(KERN_ERR MODULE_NAME": unable to start kernel thread\n"); kfree(udp_thread); udp_thread = NULL; return -ENOMEM; } printk(KERN_INFO MODULE_NAME": kspeedtest_init: udp bidirect thread started \n"); return 0; } static int start_udp_bidirect_thread(void) { if (udp_bidirect_thread != NULL && udp_bidirect_thread->thread != NULL) { printk(KERN_ERR MODULE_NAME": udp bidirect thread already started!\n"); return -1; } if (udp_bidirect_thread) kfree(udp_bidirect_thread); udp_bidirect_thread = kmalloc(sizeof(struct kthread_t), GFP_KERNEL); if (!udp_bidirect_thread) { printk(KERN_ERR MODULE_NAME": unable to allocate memory for udp_bidirect_thread.\n"); return -ENOMEM; } memset(udp_bidirect_thread, 0, sizeof(struct kthread_t)); udp_bidirect_thread->thread = kthread_run((void *)kspeedtest_udp_bidirect_thread, NULL, MODULE_NAME); if (IS_ERR(udp_bidirect_thread->thread)) { printk(KERN_ERR MODULE_NAME": unable to start kernel thread\n"); kfree(udp_bidirect_thread); udp_bidirect_thread = NULL; return -ENOMEM; } printk(KERN_INFO MODULE_NAME": kspeedtest_init: udp bidirect thread started \n"); return 0; } static int start_speedtest_thread(void) { if (speedtest_thread != NULL && speedtest_thread->thread != NULL) { printk(KERN_ERR MODULE_NAME": speedtest thread already started!\n"); return -1; } if (speedtest_thread) kfree(speedtest_thread); speedtest_thread = kmalloc(sizeof(struct kthread_t), GFP_KERNEL); if (!speedtest_thread) { printk(KERN_ERR MODULE_NAME": unable to allocate memory for speedtest_thread.\n"); return -ENOMEM; } memset(speedtest_thread, 0, sizeof(struct kthread_t)); speedtest_thread->thread = kthread_run((void *)kspeedtest_speedtest_thread, NULL, MODULE_NAME); if (IS_ERR(speedtest_thread->thread)) { printk(KERN_ERR MODULE_NAME": unable to start kernel thread\n"); kfree(speedtest_thread); speedtest_thread = NULL; return -ENOMEM; } printk(KERN_INFO MODULE_NAME": kspeedtest_init: speedtest thread started \n"); return 0; } static int stop_udp_thread(void) { if (udp_thread == NULL) return 0; if (udp_thread->thread) { (void)send_sig(SIGKILL, udp_thread->thread, 1); } if (udp_thread->thread==NULL) printk(KERN_ERR MODULE_NAME": no kernel thread to kill\n"); else { while(udp_thread->running) msleep(10); printk(KERN_INFO MODULE_NAME": successfully killed kernel udp thread\n"); } kfree(udp_thread); udp_thread = NULL; return 0; } static int stop_udp_bidirect_thread(void) { if (udp_bidirect_thread == NULL) return 0; if (udp_bidirect_thread->thread) { (void)send_sig(SIGKILL, udp_bidirect_thread->thread, 1); } if (udp_bidirect_thread->thread==NULL) printk(KERN_ERR MODULE_NAME": no kernel thread to kill\n"); else { while(udp_bidirect_thread->running) msleep(10); printk(KERN_INFO MODULE_NAME": successfully killed kernel udp bidirect thread\n"); } kfree(udp_bidirect_thread); udp_bidirect_thread = NULL; return 0; } static int stop_speedtest_thread(void) { if (speedtest_thread == NULL) return 0; if (speedtest_thread->thread) { (void)send_sig(SIGKILL, speedtest_thread->thread, 1); } if (speedtest_thread->thread==NULL) printk(KERN_ERR MODULE_NAME": no kernel thread to kill\n"); else { while(speedtest_thread->running) msleep(10); printk(KERN_INFO MODULE_NAME": successfully killed kernel speedtest thread\n"); } kfree(speedtest_thread); speedtest_thread = NULL; return 0; } /***************************************************************************/ /* TCP */ /***************************************************************************/ static int tcp_receive(struct socket* sock, struct sockaddr_in6* addr, unsigned char* buf, int len) { struct msghdr msg; struct iovec iov; mm_segment_t oldfs; int size = 0; if (sock->sk==NULL) return 0; iov.iov_base = buf; iov.iov_len = len; msg.msg_flags = MSG_MORE | MSG_DONTWAIT; msg.msg_name = addr; msg.msg_namelen = sizeof(struct sockaddr_in); msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = NULL; oldfs = get_fs(); set_fs(KERNEL_DS); size = sock_recvmsg(sock,&msg,len,msg.msg_flags); set_fs(oldfs); return size; } static int tcp_handle_data(struct kthread_t *kthread, struct socket *client) { int size = 0; int cnt = 0; int bufsize = 1500; unsigned char buf[bufsize+1]; #if 0 switch(kthread->client_state) { case client_init: break; case client_listen: { #ifdef NO_AUTO_RWIN int rwin = 256000; #endif err = sock_create_lite(AF_INET6, SOCK_STREAM, IPPROTO_TCP, &kthread->client); if (err || !kthread->client) { printk(KERN_ERR MODULE_NAME": error creating client socket"); return -1; } kthread->client->type = kthread->sock->type; kthread->client->ops = kthread->sock->ops; err = kthread->sock->ops->accept(kthread->sock, kthread->client, 0); if (err < 0) { sock_release(kthread->client); kthread->client = NULL; printk(KERN_ERR MODULE_NAME": error accepting client connection"); return -2; } printk(KERN_INFO MODULE_NAME": client connection accepted \n "); #ifdef NO_AUTO_RWIN err = sock_setsockopt(kthread->client, SOL_SOCKET, SO_RCVBUF, (char *)&rwin, sizeof(rwin)); printk(KERN_INFO MODULE_NAME": setting rwin %d ret=%d\n", rwin, err); #endif kthread->client_state = client_connected; kspeedtest.seconds = 0; kspeedtest.total_bytes = 0; kspeedtest.total_packets = 0; } break; case client_connected: #endif if (client->sk->sk_state == TCP_CLOSE_WAIT) { printk(KERN_INFO MODULE_NAME": client connection closed\n"); return -1; } else { // tcp_receive is non-blocking now - realize a timeout of 1,5 seconds instead. while (size == 0) { size = tcp_receive(client, &kthread->addr, buf, bufsize); if (cnt < 3000 && size == -11) { cnt++; size = 0; udelay(500); } } } if (size < 0) { printk(KERN_ERR MODULE_NAME": error getting bytes, sock_recvmsg error = %d\n", size); printk(KERN_INFO MODULE_NAME": client connection closed\n"); } else { kspeedtest.bytes += size; kspeedtest.packets++; udelay(5); } return size; } int kspeedtest_tcp_worker(void) { printk(KERN_ERR "kspeedtest_tcp_worker fired\n"); mutex_lock(&kspeedtest_mutex); { tcp_thread->running = 1; current->flags |= PF_NOFREEZE; daemonize("kspeedtest_tcp"); allow_signal(SIGKILL); } mutex_unlock(&kspeedtest_mutex); while (tcp_thread && tcp_thread->running == 1) { struct socket_list **pp; int count = 0; if (signal_pending(current)) break; for (pp = &tcp_thread->clients; *pp; ) { if ((*pp)->sock) { if (tcp_handle_data(tcp_thread, (*pp)->sock) < 0) { struct socket_list *p = *pp; sock_release((*pp)->sock); *pp = (*pp)->next; printk("delete client \n"); kfree(p); continue; } } pp = &(*pp)->next; count++; } if (count == 0) msleep(1000); } return 0; } static int kspeedtest_tcp_thread(void) { int err; #ifdef NO_AUTO_RWIN int rwin = 256000; #endif struct inet_connection_sock *isock; DECLARE_WAITQUEUE(wait, current); mutex_lock(&kspeedtest_mutex); { /* kernel thread initialization */ tcp_thread->running = 1; current->flags |= PF_NOFREEZE; /* daemonize (take care with signals, after daemonize() they are disabled) */ daemonize(MODULE_NAME); allow_signal(SIGKILL); } mutex_unlock(&kspeedtest_mutex); /* create a tcp socket */ if ((err = sock_create_kern(AF_INET6, SOCK_STREAM, IPPROTO_TCP, &tcp_thread->sock)) < 0) { printk(KERN_ERR MODULE_NAME": Could not create a stream socket, error = %d\n", -ENXIO); goto out; } memset(&tcp_thread->addr, 0, sizeof(struct sockaddr)); tcp_thread->addr.sin6_family = AF_INET6; tcp_thread->addr.sin6_addr = in6addr_any; tcp_thread->addr.sin6_port = htons(kspeedtest.tcp_port ? kspeedtest.tcp_port : DEFAULT_TCP_PORT); #ifdef NO_AUTO_RWIN err = sock_setsockopt(tcp_thread->sock, SOL_SOCKET, SO_RCVBUF, (char *)&rwin, sizeof(rwin)); printk(KERN_INFO MODULE_NAME": rwin %d ret=%d\n", rwin, err); #endif if ( (err = tcp_thread->sock->ops->bind(tcp_thread->sock, (struct sockaddr *)&tcp_thread->addr, sizeof(struct sockaddr_in6) ) ) < 0) { printk(KERN_ERR MODULE_NAME": Could not bind or connect to tcp socket, error = %d\n", -err); goto close_and_out; } //tcp_thread->sock->sk->sk_reuse = 1; tcp_thread->sock->ops->listen(tcp_thread->sock, 48); //why 48? tcp_thread->client_state = client_listen; printk(KERN_INFO MODULE_NAME"(tcp_thread): listening on port %d\n", ntohs(tcp_thread->addr.sin6_port)); tcp_thread->worker = kthread_run((void *)kspeedtest_tcp_worker, NULL, MODULE_NAME); isock = inet_csk(tcp_thread->sock->sk); while (err >= 0) { int clientCnt = 0; struct socket *client_sock; struct socket_list **pp, *p; if (signal_pending(current)) break; // count sockets first - there is a maximum for (pp = &tcp_thread->clients; *pp; pp = &(*pp)->next) clientCnt++; if (clientCnt >= MAXIMUM_TCP_PARALLEL_CONNECTIONS) { msleep(300); continue; } if (reqsk_queue_empty(&isock->icsk_accept_queue)) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) add_wait_queue(sk_sleep(tcp_thread->sock->sk), &wait); #else add_wait_queue(tcp_thread->sock->sk->sk_sleep, &wait); #endif __set_current_state(TASK_INTERRUPTIBLE); schedule_timeout(HZ); __set_current_state(TASK_RUNNING); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 39) remove_wait_queue(sk_sleep(tcp_thread->sock->sk), &wait); #else remove_wait_queue(tcp_thread->sock->sk->sk_sleep, &wait); #endif continue; } err = sock_create_lite(AF_INET6, SOCK_STREAM, IPPROTO_TCP, &client_sock); if (err || !client_sock) { printk(KERN_ERR MODULE_NAME"(tcp_thread): error creating client socket"); break; } client_sock->type = tcp_thread->sock->type; client_sock->ops = tcp_thread->sock->ops; for (pp = &tcp_thread->clients; *pp; pp = &(*pp)->next) ; p = kmalloc(sizeof(struct socket_list), GFP_KERNEL); if (!p) { printk(KERN_ERR MODULE_NAME"(tcp_thread): couldn't allocate memory for socket_list."); break; } p->sock = client_sock; p->next = 0; printk(KERN_ERR MODULE_NAME"(tcp_thread): accept new connection \n"); err = tcp_thread->sock->ops->accept(tcp_thread->sock, client_sock, O_NONBLOCK); *pp = p; } close_and_out: sock_release(tcp_thread->sock); tcp_thread->sock = NULL; out: tcp_thread->thread = NULL; tcp_thread->running = 0; return 0; } static int start_tcp_thread(void) { if (tcp_thread != NULL && tcp_thread->thread != NULL) { printk(KERN_ERR MODULE_NAME": tcp thread already started!\n"); return -1; } if (tcp_thread) kfree(tcp_thread); tcp_thread = kmalloc(sizeof(struct kthread_t), GFP_KERNEL); if (!tcp_thread) { printk(KERN_ERR MODULE_NAME": unable to allocate memory for tcp_thread.\n"); return -ENOMEM; } memset(tcp_thread, 0, sizeof(struct kthread_t)); tcp_thread->thread = kthread_run((void *)kspeedtest_tcp_thread, NULL, MODULE_NAME); tcp_thread->client_state = client_init; if (IS_ERR(tcp_thread->thread)) { printk(KERN_ERR MODULE_NAME": unable to start kernel thread\n"); kfree(tcp_thread); tcp_thread = NULL; return -ENOMEM; } printk(KERN_INFO MODULE_NAME": kspeedtest_init: tcp thread started \n"); return 0; } static int stop_tcp_thread(void) { struct socket_list *sl; if (tcp_thread == NULL) return 0; for(sl = tcp_thread->clients; sl; sl = sl->next) { struct socket_list *tmp; if (sl->sock) sock_release(sl->sock); tmp = sl; sl = sl->next; kfree(tmp); } if (tcp_thread->thread) { (void)send_sig(SIGKILL, tcp_thread->thread, 1); } if (tcp_thread->worker) { (void)send_sig(SIGKILL, tcp_thread->worker, 1); } if (tcp_thread->thread==NULL) printk(KERN_ERR MODULE_NAME": no kernel thread to kill\n"); else { while(tcp_thread->running) msleep(10); printk(KERN_INFO MODULE_NAME": successfully killed kernel tcp thread\n"); } kfree(tcp_thread); tcp_thread = NULL; return 0; } /*********************************************************************************/ static int __init kspeedtest_init(void) { init_timer(&ktimer); ktimer.function = Second; ktimer.data = 0; ktimer.expires = jiffies + HZ; add_timer(&ktimer); printk(KERN_INFO MODULE_NAME": kspeedtest_init: second timer started \n"); kspeedtest.null_vpid = -1; kspeedtest.test_calculation_seconds = -1; reset_stats(); #ifdef CONFIG_PROC_FS kspeedtest_proc_init(); #endif printk(KERN_INFO MODULE_NAME": kspeedtest_init done\n"); return 0; } static void __exit kspeedtest_exit(void) { del_timer_sync(&ktimer); stop_udp_thread(); stop_udp_bidirect_thread(); stop_speedtest_thread(); stop_tcp_thread(); #ifdef CONFIG_PROC_FS kspeedtest_proc_exit(); #endif printk(KERN_INFO MODULE_NAME": module unloaded\n"); } #ifdef CONFIG_PROC_FS /* -------------------------------------------------------------------- */ static int stats_show(struct seq_file *m, void *v) { kspeedtest_show_stats((my_fprintf *)seq_printf, m); return 0; } static int stats_show_open(struct inode *inode, struct file *file) { return single_open(file, stats_show, PDE(inode)->data); } static const struct file_operations stats_show_fops = { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32) .owner = THIS_MODULE, #endif .open = stats_show_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; static int connections_show(struct seq_file *m, void *v) { if (tcp_thread && tcp_thread->running) { struct socket_list *sl; int tcpclients = 0; for(sl = tcp_thread->clients; sl; sl = sl->next) tcpclients++; seq_printf(m, "TCP %sconnected %d\n", tcpclients > 0 ? "" : "dis", kspeedtest.tcp_port ? kspeedtest.tcp_port : DEFAULT_TCP_PORT); } else { seq_printf(m, "TCP off\n"); } if (udp_thread && udp_thread->running) { seq_printf(m, "UDP %s %d\n", kspeedtest.udp_received == 1 ? "connected" : "disconnected", kspeedtest.udp_port ? kspeedtest.udp_port : DEFAULT_UDP_PORT); } else { seq_printf(m, "UDP off\n"); } if (udp_bidirect_thread && udp_bidirect_thread->running) { seq_printf(m, "UDP bidirect %s %d\n", kspeedtest.udp_bidirect_received == 1 ? "connected" : "disconnected", kspeedtest.udp_bidirect_port ? kspeedtest.udp_bidirect_port : DEFAULT_UDP_BIDIRECT_PORT); } else { seq_printf(m, "UDP bidirect off\n"); } if (speedtest_thread && speedtest_thread->running) { seq_printf(m, "Speedtest %s %d\n", kspeedtest.test_state, kspeedtest.test_port ? kspeedtest.test_port : DEFAULT_SPEEDTEST_PORT); } else { seq_printf(m, "Speedtest off\n"); } return 0; } static int connections_show_open(struct inode *inode, struct file *file) { return single_open(file, connections_show, PDE(inode)->data); } static const struct file_operations connections_show_fops = { #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32) .owner = THIS_MODULE, #endif .open = connections_show_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; static int kspeedtest_write_cmds (struct file *file, const char *buffer, unsigned long count, void *data) { char cmd[100]; char *argv[10]; int argc = 0; char *ptr_cmd; char *delimitters = " \n\t"; char *ptr_next_tok; if (count > 100) count = 100; memset((void *)&cmd[0], 0, sizeof(cmd)); memset((void *)&argv[0], 0, sizeof(argv)); if (copy_from_user(&cmd, buffer, count)) return -EFAULT; ptr_next_tok = &cmd[0]; ptr_cmd = strsep(&ptr_next_tok, delimitters); if (ptr_cmd == NULL) return -1; do { argv[argc++] = ptr_cmd; if (argc >= 10) { printk(KERN_ERR MODULE_NAME": too many parameters. dropping cmd.\n"); return -EIO; } ptr_cmd = strsep(&ptr_next_tok, delimitters); } while (ptr_cmd != NULL); if (strcmp(argv[0], "start") == 0) { if (argv[1] && strcmp(argv[1], "tcp") == 0) { start_tcp_thread(); kspeedtest.tcp_port = simple_strtoul(argv[2], 0, 10);; } else if (argv[1] && strcmp(argv[1], "udp") == 0) { kspeedtest.udp_port = simple_strtoul(argv[2], 0, 10);; start_udp_thread(); } else if (argv[1] && strcmp(argv[1], "udp_bidirect") == 0) { kspeedtest.udp_bidirect_port = simple_strtoul(argv[2], 0, 10); start_udp_bidirect_thread(); } else if (argv[1] && strcmp(argv[1], "speedtest") == 0) { kspeedtest.test_port = argv[2] ? simple_strtoul(argv[2], 0, 10) : DEFAULT_SPEEDTEST_PORT; kspeedtest.test_duration_download = argv[3] ? simple_strtoul(argv[3], 0, 10) : DEFAULT_SPEEDTEST_DURATION_DOWN; kspeedtest.test_duration_upload = argv[4] ? simple_strtoul(argv[4], 0, 10) : DEFAULT_SPEEDTEST_DURATION_UP; start_speedtest_thread(); } else { printk(KERN_ERR MODULE_NAME": command wrong or incomplete (usage example: 'start tcp 4711'\n"); } } else if (strcmp(argv[0], "stop") == 0) { if (argv[1] && strcmp(argv[1], "tcp") == 0) { stop_tcp_thread(); } else if (argv[1] && strcmp(argv[1], "udp") == 0) { stop_udp_thread(); } else if (argv[1] && strcmp(argv[1], "udp_bidirect") == 0) { stop_udp_bidirect_thread(); } else if (argv[1] && strcmp(argv[1], "speedtest") == 0) { stop_speedtest_thread(); } else if (argv[1] && strcmp(argv[1], "all") == 0) { stop_tcp_thread(); stop_udp_thread(); stop_udp_bidirect_thread(); stop_speedtest_thread(); } else { printk(KERN_ERR MODULE_NAME": command wrong or incomplete (usage example: 'stop tcp'\n"); } } else if (strcmp(argv[0], "sleepdelay") == 0) { kspeedtest.sleepdelay = simple_strtoul(argv[1], 0, 10);; } else if (strcmp(argv[0], "verbose") == 0) { int level = 1; if (argv[1] && *argv[1]) level = simple_strtoul(argv[1], 0, 10); kspeedtest.verbose = level; printk(KERN_ERR MODULE_NAME": verbose %d \n", level); } else if (strcmp(argv[0], "noverbose") == 0) { kspeedtest.verbose = 0; } else if (strcmp(argv[0], "reset") == 0) { reset_stats(); } else { printk(KERN_ERR MODULE_NAME": wrong command\n"); } return count; } static struct proc_dir_entry *dir_entry = 0; static void __init kspeedtest_proc_init(void) { struct proc_dir_entry *file_entry; dir_entry = proc_net_mkdir(&init_net, "kspeedtest", init_net.proc_net); if (dir_entry) { dir_entry->read_proc = 0; dir_entry->write_proc = 0; } file_entry = create_proc_entry("control", S_IFREG|S_IWUSR, dir_entry); if (file_entry) { file_entry->data = NULL; file_entry->read_proc = NULL; file_entry->write_proc = kspeedtest_write_cmds; #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32) file_entry->owner = THIS_MODULE; #endif } proc_create("stats", S_IRUGO, dir_entry, &stats_show_fops); proc_create("connections", S_IRUGO, dir_entry, &connections_show_fops); } static void __init kspeedtest_proc_exit(void) { remove_proc_entry("control", dir_entry); remove_proc_entry("stats", dir_entry); remove_proc_entry("connections", dir_entry); remove_proc_entry("kspeedtest", init_net.proc_net); } #endif /* -------------------------------------------------------------------- */ /* init and cleanup functions */ module_init(kspeedtest_init); module_exit(kspeedtest_exit); /* module information */ MODULE_DESCRIPTION("kernel speedtest"); /* -------------------------------------------------------------------- */ #include #include #include MODULE_INFO(vermagic, VERMAGIC_STRING); #undef unix struct module __this_module __attribute__((section(".gnu.linkonce.this_module"))) = { // .name = __stringify(KBUILD_MODNAME), .name = "kspeedtest", .init = init_module, #ifdef CONFIG_MODULE_UNLOAD .exit = cleanup_module, #endif };