之前发过一个练手的程序 udp packet 的收发(sk_buff+Netfiler)。这次在内核下发送 SYN 段的方法大致与上次一样。上次是在内核下收发 UDP 包,这次发送的是 TCP 包。主要熟悉了 TCP 头和选项在 Linux 内核是如何填充的。
主要参考了 tcp_output.c
这个文件里面的代码。程序中主要要清楚一些结构的用法:sk_buf
、tcphdr
、iphdr
等。还有一些函数的调用,如:ip_hdr()
、tcp_hdr()
等。
代码如下:
/***********************************************************************
* File: sendSYN.c
* Author: Sam
* Create Date: 2011/12/8
* Abstract Description:
* Send a SYN to a web server.
* Note:
* Client information:
* Ubuntu 10.04, AMD Althon*2;
* IP address: *.*.*.*;
* Hardware address:*:*:*:*:*:*.
* Server information:
* Xubuntu 11.10, Intel Atom;
* IP address: *.*.*.*;
* Hardware address: *:*:*:*:*:*.
***********************************************************************/
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/init.h>
#include <linux/workqueue.h>
#include <linux/in.h>
#include <linux/inet.h>
#include <linux/socket.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <net/tcp.h>
#include <net/sock.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#define IF_NAME "eth0"
#define CLIENTIP "*.*.*.*"
#define SERVERIP "*.*.*.*"
#define CLIENTPORT 80
#define SERVERPORT 80
#define SERVER_MAC {*, *, *, *, *, *}
#define CLIENT_MAC {*, *, *, *, *, *}
#define MSS 1460
struct socket *sock;
static void sock_init()
{
struct ifreq ifr;
sock_create_kern(PF_INET, SOCK_STREAM, 0, &sock);
strcpy(ifr.ifr_ifrn.ifrn_name, IF_NAME);
kernel_sock_ioctl(sock, SIOCSIFNAME, (unsigned long) &ifr);
}
static void send_packet()
{
struct net_device *netdev = NULL;
struct net *net = NULL;
struct sk_buff *skb = NULL;
struct ethhdr *eth_header = NULL;
struct iphdr *ip_header = NULL;
struct tcphdr *tcp_header = NULL;
unsigned tcp_options_size, tcp_header_size;
__be32 dip = in_aton(SERVERIP);
__be32 sip = in_aton(CLIENTIP);
u16 data_len = 0;
u32 skb_len;
u8 dst_mac[ETH_ALEN] = SERVER_MAC;
u8 src_mac[ETH_ALEN] = CLIENT_MAC;
u8 flags = TCPCB_FLAG_SYN; // Defined in tcp.h->tcp_skb_cb
__be32 *ptr; // used to write the TCP options.
/* construct skb
* sock_net() is defined in include/net/sock.h
* dev_get_by_name()函数用来取得设备指针,使用该函数
* 后一定要使用dev_put()函数取消设备引用. */
sock_init();
net = sock_net((const struct sock *) sock->sk);
netdev = dev_get_by_name(net, IF_NAME);
tcp_options_size = TCPOLEN_MSS;
tcp_header_size = tcp_options_size + sizeof(struct tcphdr);
/* LL_RESERVED_SPACE is defined in include/netdevice.h. */
skb_len = data_len + sizeof(struct iphdr)
+ tcp_header_size + LL_RESERVED_SPACE(netdev);
/* 打印IP header, TCP header 和整个skb的长度. */
printk("----raw packet information----\n");
printk("iphdr: %d\n", sizeof(struct iphdr));
printk("tcphdr: %d\n", tcp_header_size);
printk("data_len: %d\n", data_len);
printk("skb_len: %d\n", skb_len);
/* dev_alloc_skb是一个缓冲区分配函数,主要被设备驱动使用.
* 这是一个alloc_skb的包装函数, 它会在请求分配的大小上增加
* 16 Bytes的空间以优化缓冲区的读写效率.*/
skb = dev_alloc_skb(skb_len);
if (!skb) {
printk("Fail to alloc skb --01\n");
return;
}
/* fill the skb.具体参照struct sk_buff.
* skb_reserve()用来为协议头预留空间.
* PACKET_OTHERHOST: packet type is "to someone else".
* ETH_P_IP: Internet Protocol packet.
* CHECKSUM_NONE表示完全由软件来执行校验和. */
skb_reserve(skb, LL_RESERVED_SPACE(netdev));
skb->dev = netdev;
skb->pkt_type = PACKET_OTHERHOST;
skb->protocol = htons(ETH_P_IP);
skb->ip_summed = CHECKSUM_NONE;
skb->priority = 0;
skb->len = data_len + LL_RESERVED_SPACE(netdev);
/* 分配内存给IP header和TCP header */
skb_set_network_header(skb, 0);
skb_put(skb, sizeof(struct iphdr));
skb_set_transport_header(skb, sizeof(struct iphdr));
/* Build TCP header in skb */
skb_put(skb, tcp_header_size);
//memset(tcp_header, 0, tcp_header_size);
tcp_header = tcp_hdr(skb);
tcp_header->source = htons(CLIENTPORT);
tcp_header->dest = htons(SERVERPORT);
tcp_header->seq = 0;
tcp_header->ack_seq = 0;
tcp_header->doff = tcp_header_size / 4;
/* Build the flags: */
*(((__be16 *)tcp_header) + 6) = htons(((tcp_header_size >> 2) << 12) | flags);
/* RFC1323: The window in SYN & SYN/ACK segments is never scaled. */
tcp_header->window = htons(65535U);
tcp_header->check = 0;
tcp_header->urg_ptr = 0;
/* Build the TCP options. (Read the tcp_transmit_skb and tcp_options_write)
* __be32 指以32bit为单位,后面的+1指的是tcp_header起始的地址以32bit为单位
* 加1,即到了options的位置了。这样就可以开始填写TCP options了. */
ptr = (__be32 *)(tcp_header + 1);
*ptr++ = htonl((TCPOPT_MSS << 24) | (TCPOLEN_MSS << 16) | MSS);
/* construct ip header in skb */
ip_header = ip_hdr(skb);
ip_header->version = 4;
ip_header->ihl = sizeof(struct iphdr) / 4;
ip_header->frag_off = 0;
ip_header->protocol = IPPROTO_TCP;
ip_header->tos = 0;
ip_header->daddr = dip;
ip_header->saddr = sip;
ip_header->ttl = 0x40;
ip_header->tot_len = htons(skb->len);
ip_header->check = 0;
/* caculate checksum */
skb->csum = skb_checksum(skb, ip_header->ihl*4, skb->len-ip_header->ihl*4, 0);
ip_header->check = ip_fast_csum(ip_header, ip_header->ihl);
tcp_header->check = csum_tcpudp_magic(sip, dip, skb->len-ip_header->ihl*4,
IPPROTO_TCP, skb->csum);
/* construct ethernet header in skb */
eth_header = (struct ethhdr *)skb_push(skb, ETH_HLEN);
memcpy(eth_header->h_dest, dst_mac, ETH_ALEN); /* destination eth addr */
memcpy(eth_header->h_source, src_mac, ETH_ALEN); /* source ether addr */
eth_header->h_proto = htons(ETH_P_IP); /* packet type ID field */
/* send packet */
if (dev_queue_xmit(skb) < 0) {
dev_put(netdev);
kfree_skb(skb);
printk("send packet by skb failed.\n");
return;
}
printk("send packet by skb success.\n");
}
int init_module(void)
{
send_packet();
printk("TCP SYN segement: init module--------ok\n");
return 0;
}
void cleanup_module(void)
{
sock_release(sock);
printk("TCP SYN segement: exit module--------ok\n");
}
MODULE_LICENSE("GPL");