kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

拥塞控制模块注意

应用改变sock的拥塞控制算法

1
2
3
4
5
#define SOL_TCP 6
#define TCP_CONGESTION  13

strcpy(name, "cubic");
setsockopt (connfd, SOL_TCP, TCP_CONGESTION, name, strlen(name));
net/socket.c
1
2
3
4
5
6
7
8
9
SYSCALL_DEFINE5(setsockopt, int, fd, int, level, int, optname,
		char __user *, optval, int, optlen)
{
	...
			err =
				sock->ops->setsockopt(sock, level, optname, optval,
						  optlen);
	...
}

对于ipv4的tcp,sock->ops指向 net/ipv4/af_inet.c 中的 inet_stream_ops,所以setsockopt等于sock_common_setsockopt。

net/core/sock.c
1
2
3
4
5
6
7
int sock_common_setsockopt(struct socket *sock, int level, int optname,
			   char __user *optval, unsigned int optlen)
{
	struct sock *sk = sock->sk;

	return sk->sk_prot->setsockopt(sk, level, optname, optval, optlen);
}

sk_prot 指向 net/ipv4/tcp_ipv4.c 中的 tcp_prot,所以setsockopt等于tcp_setsockopt

net/ipv4/tcp.c
1
2
3
4
5
6
7
8
9
10
int tcp_setsockopt(struct sock *sk, int level, int optname, char __user *optval,
		   unsigned int optlen)
{
	struct inet_connection_sock *icsk = inet_csk(sk);

	if (level != SOL_TCP)
		return icsk->icsk_af_ops->setsockopt(sk, level, optname,
							 optval, optlen);
	return do_tcp_setsockopt(sk, level, optname, optval, optlen);
}

因为level = SOL_TCP, optname = TCP_CONGESTION, 所以直接到do_tcp_setsockopt的第一个if里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static int do_tcp_setsockopt(struct sock *sk, int level,
		int optname, char __user *optval, unsigned int optlen)
{
	struct tcp_sock *tp = tcp_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk); 
	int val;    
	int err = 0;

	/* This is a string value all the others are int's */
	if (optname == TCP_CONGESTION) {    
		char name[TCP_CA_NAME_MAX]; 

		if (optlen < 1)
			return -EINVAL;

		val = strncpy_from_user(name, optval,
					min_t(long, TCP_CA_NAME_MAX-1, optlen));
		if (val < 0)
			return -EFAULT;
		name[val] = 0;

		lock_sock(sk);
		err = tcp_set_congestion_control(sk, name);
		release_sock(sk);
		return err;
	}

...

net/ipv4/tcp_cong.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* Change congestion control for socket */
int tcp_set_congestion_control(struct sock *sk, const char *name)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_congestion_ops *ca;
	int err = 0;

	rcu_read_lock();
	ca = tcp_ca_find(name);

	/* no change asking for existing value */
	if (ca == icsk->icsk_ca_ops)
		goto out;

#ifdef CONFIG_MODULES
	/* not found attempt to autoload module */
	if (!ca && capable(CAP_NET_ADMIN)) {
		rcu_read_unlock();
		request_module("tcp_%s", name);
		rcu_read_lock();
		ca = tcp_ca_find(name);
	}
#endif
	if (!ca)
		err = -ENOENT;

	else if (!((ca->flags & TCP_CONG_NON_RESTRICTED) || capable(CAP_NET_ADMIN)))
		err = -EPERM;

	else if (!try_module_get(ca->owner))
		err = -EBUSY;

	else {
		tcp_cleanup_congestion_control(sk);
		icsk->icsk_ca_ops = ca;

		if (sk->sk_state != TCP_CLOSE && icsk->icsk_ca_ops->init) // 如果sk->sk_state = TCP_CLOSE, 那么不会调用拥塞控制模块的初始化
			icsk->icsk_ca_ops->init(sk);
	}
 out:
	rcu_read_unlock();
	return err;
}

可以看到,如果sk->sk_state = TCP_CLOSE, 那么不会调用拥塞控制模块的初始化。


那么什么时候sk->sk_state == TCP_CLOSE,并且还能调用setsockopt呢?

举一种情况:当收到RST包的时候,tcp_rcv_established()->tcp_validate_incoming()->tcp_reset()->tcp_done()将sk置为TCP_CLOSE。
如果拥塞控制模块中init有申请内存,release中释放内存。那么在上述情况下将会出现没有申请而直接释放的情况,导致panic。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BUG: unable to handle kernel paging request at ffffeba4000002a0

[<ffffffff8115b17e>] kfree+0x6e/0x240
[<ffffffffa0068055>] cong_release+0x35/0x50 [cong]
[<ffffffff81467953>] tcp_cleanup_congestion_control+0x23/0x40
[<ffffffff81465bb9>] tcp_v4_destroy_sock+0x29/0x2d0
[<ffffffff8144e9e3>] inet_csk_destroy_sock+0x53/0x140
[<ffffffff814504c0>] tcp_close+0x340/0x4a0
[<ffffffff814748de>] inet_release+0x5e/0x90
[<ffffffff813f4359>] sock_release+0x29/0x90
[<ffffffff813f43d7>] sock_close+0x17/0x40
[<ffffffff81173ed3>] __fput+0xf3/0x220
[<ffffffff8117401c>] fput+0x1c/0x30
[<ffffffff8116df2d>] filp_close+0x5d/0x90
[<ffffffff8117090c>] sys_close+0xac/0x110
[<ffffffff8100af72>] system_call_fastpath+0x16/0x1b

测试代码

congestion_mod_panic

TCP校验和的原理和实现

http://blog.csdn.net/zhangskd/article/details/11770647

概述

TCP校验和是一个端到端的校验和,由发送端计算,然后由接收端验证。其目的是为了发现TCP首部和数据在发送端到接收端之间发生的任何改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃。

TCP校验和覆盖TCP首部和TCP数据,而IP首部中的校验和只覆盖IP的首部,不覆盖IP数据报中的任何数据。

TCP的校验和是必需的,而UDP的校验和是可选的。

TCP和UDP计算校验和时,都要加上一个12字节的伪首部。

伪首部

伪首部共有12字节,包含如下信息:源IP地址、目的IP地址、保留字节(置0)、传输层协议号(TCP是6)、TCP报文长度(报头+数据)。

伪首部是为了增加TCP校验和的检错能力:如检查TCP报文是否收错了(目的IP地址)、传输层协议是否选对了(传输层协议号)等。

定义

(1) RFC 793的TCP校验和定义

The checksum field is the 16 bit one’s complement of the one’s complement sum of all 16-bit words in the header and text. If a segment contains an odd number of header and text octets to be checksummed, the last octet is padded on the right with zeros to form a 16-bit word for checksum purposes. The pad is not transmitted as part of the segment. While computing the checksum, the checksum field itself is replaced with zeros.

上述的定义说得很明确:
首先,把伪首部、TCP报头、TCP数据分为16位的字,如果总长度为奇数个字节,则在最后增添一个位都为0的字节。把TCP报头中的校验和字段置为0(否则就陷入鸡生蛋还是蛋生鸡的问题)。

其次,用反码相加法累加所有的16位字(进位也要累加)。

最后,对计算结果取反,作为TCP的校验和。

(2) RFC 1071的IP校验和定义

1.Adjacent octets to be checksummed are paired to form 16-bit integers, and the 1’s complement sum of these 16-bit integers is formed.

2.To generate a checksum, the checksum field itself is cleared, the 16-bit 1’s complement sum is computed over the octets concerned, and the 1’s complement of this sum is placed in the checksum field.

3.To check a checksum, the 1’s complement sum is computed over the same set of octets, including the checksum field. If the result is all 1 bits (-0 in 1’s complement arithmetic), the check succeeds.

可以看到,TCP校验和、IP校验和的计算方法是基本一致的,除了计算的范围不同。

实现

基于2.6.18、x86_64。

csum_tcpudp_nofold()按4字节累加伪首部到sum中。

1
2
3
4
5
6
7
8
9
10
11
12
static inline unsigned long csum_tcpudp_nofold (unsigned long saddr, unsigned long daddr,  
						unsigned short len, unsigned short proto,  
						unsigned int sum)  
{  
	asm("addl %1, %0\n"    /* 累加daddr */  
		"adcl %2, %0\n"    /* 累加saddr */  
		"adcl %3, %0\n"    /* 累加len(2字节), proto, 0*/  
		"adcl $0, %0\n"    /*加上进位 */  
		: "=r" (sum)  
		: "g" (daddr), "g" (saddr), "g" ((ntohs(len) << 16) + proto*256), "0" (sum));  
	return sum;  
}   

csum_tcpudp_magic()产生最终的校验和。

首先,按4字节累加伪首部到sum中。

其次,累加sum的低16位、sum的高16位,并且对累加的结果取反。

最后,截取sum的高16位,作为校验和。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
static inline unsigned short int csum_tcpudp_magic(unsigned long saddr, unsigned long daddr,  
							unsigned short len, unsigned short proto,  
							unsigned int sum)  
{  
	return csum_fold(csum_tcpudp_nofold(saddr, daddr, len, proto, sum));  
}  
  
static inline unsigned int csum_fold(unsigned int sum)  
{  
	__asm__(  
		"addl %1, %0\n"  
		"adcl 0xffff, %0"  
		: "=r" (sum)  
		: "r" (sum << 16), "0" (sum & 0xffff0000)   
  
		/* 将sum的低16位,作为寄存器1的高16位,寄存器1的低16位补0。 
		  * 将sum的高16位,作为寄存器0的高16位,寄存器0的低16位补0。 
		  * 这样,addl %1, %0就累加了sum的高16位和低16位。 
		  * 
		 * 还要考虑进位。如果有进位,adcl 0xfff, %0为:0x1 + 0xffff + %0,寄存器0的高16位加1。 
		  * 如果没有进位,adcl 0xffff, %0为:0xffff + %0,对寄存器0的高16位无影响。 
		  */  
  
	);  
  
	return (~sum) >> 16; /* 对sum取反,返回它的高16位,作为最终的校验和 */  
}  

发送校验

1
2
3
4
5
#define CHECKSUM_NONE 0 /* 不使用校验和,UDP可选 */  
#define CHECKSUM_HW 1 /* 由硬件计算报头和首部的校验和 */  
#define CHECKSUM_UNNECESSARY 2 /* 表示不需要校验,或者已经成功校验了 */  
#define CHECKSUM_PARTIAL CHECKSUM_HW  
#define CHECKSUM_COMPLETE CHECKSUM_HW  
@tcp_transmit_skb()
icsk->icsk_af_ops->send_check(sk, skb->len, skb); /* 计算校验和 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void tcp_v4_send_check(struct sock *sk, int len, struct sk_buff *skb)  
{  
	struct inet_sock *inet = inet_sk(sk);  
	struct tcphdr *th = skb->h.th;  
   
	if (skb->ip_summed == CHECKSUM_HW) {  
		/* 只计算伪首部,TCP报头和TCP数据的累加由硬件完成 */  
		th->check = ~tcp_v4_check(th, len, inet->saddr, inet->daddr, 0);  
		skb->csum = offsetof(struct tcphdr, check); /* 校验和值在TCP首部的偏移 */  
  
	} else {  
		/* tcp_v4_check累加伪首部,获取最终的校验和。 
		 * csum_partial累加TCP报头。 
		 * 那么skb->csum应该是TCP数据部分的累加,这是在从用户空间复制时顺便累加的。 
		 */  
		th->check = tcp_v4_check(th, len, inet->saddr, inet->daddr,  
					csum_partial((char *)th, th->doff << 2, skb->csum));  
	}  
}  
1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned csum_partial(const unsigned char *buff, unsigned len, unsigned sum)  
{  
	return add32_with_carry(do_csum(buff, len), sum);  
}  
  
static inline unsigned add32_with_carry(unsigned a, unsigned b)  
{  
	asm("addl %2, %0\n\t"  
		 "adcl $0, %0"  
		 : "=r" (a)  
		 : "0" (a), "r" (b));  
	return a;  
}   

do_csum()用于计算一段内存的校验和,这里用于累加TCP报头。

具体计算时用到一些技巧:
1.反码累加时,按16位、32位、64位来累加的效果是一样的。
2.使用内存对齐,减少内存操作的次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
static __force_inline unsigned do_csum(const unsigned char *buff, unsigned len)  
{  
	unsigned odd, count;  
	unsigned long result = 0;  
  
	if (unlikely(len == 0))  
		return result;  
  
	/* 使起始地址为XXX0,接下来可按2字节对齐 */  
	odd = 1 & (unsigned long) buff;  
	if (unlikely(odd)) {  
		result = *buff << 8; /* 因为机器是小端的 */  
		len--;  
		buff++;  
	}  
	count = len >> 1; /* nr of 16-bit words,这里可能余下1字节未算,最后会处理*/  
  
	if (count) {  
		/* 使起始地址为XX00,接下来可按4字节对齐 */  
		if (2 & (unsigned long) buff) {  
			result += *(unsigned short *)buff;  
			count--;  
			len -= 2;  
			buff += 2;  
		}  
		count >>= 1; /* nr of 32-bit words,这里可能余下2字节未算,最后会处理 */  
  
		if (count) {  
			unsigned long zero;  
			unsigned count64;  
			/* 使起始地址为X000,接下来可按8字节对齐 */  
			if (4 & (unsigned long)buff) {  
				result += *(unsigned int *)buff;  
				count--;  
				len -= 4;  
				buff += 4;  
			}  
			count >>= 1; /* nr of 64-bit words,这里可能余下4字节未算,最后会处理*/  
  
			/* main loop using 64byte blocks */  
			zero = 0;  
			count64 = count >> 3; /* 64字节的块数,这里可能余下56字节未算,最后会处理 */  
			while (count64) { /* 反码累加所有的64字节块 */  
				asm ("addq 0*8(%[src]), %[res]\n\t"    /* b、w、l、q分别对应8、16、32、64位操作 */  
					"addq 1*8(%[src]), %[res]\n\t"    /* [src]为指定寄存器的别名,效果应该等同于0、1等 */  
					"adcq 2*8(%[src]), %[res]\n\t"  
					"adcq 3*8(%[src]), %[res]\n\t"  
					"adcq 4*8(%[src]), %[res]\n\t"  
					"adcq 5*8(%[src]), %[res]\n\t"  
					"adcq 6*8(%[src]), %[res]\n\t"  
					"adcq 7*8(%[src]), %[res]\n\t"  
					"adcq %[zero], %[res]"  
					: [res] "=r" (result)  
					: [src] "r" (buff), [zero] "r" (zero), "[res]" (result));  
				buff += 64;  
				count64--;  
			}  
  
			/* 从这里开始,反序处理之前可能漏算的字节 */  
  
			/* last upto 7 8byte blocks,前面按8个8字节做计算单位,所以最多可能剩下7个8字节 */  
			count %= 8;  
			while (count) {  
				asm ("addq %1, %0\n\t"  
					 "adcq %2, %0\n"  
					 : "=r" (result)  
					 : "m" (*(unsigned long *)buff), "r" (zero), "0" (result));  
				--count;  
				buff += 8;  
			}  
  
			/* 带进位累加result的高32位和低32位 */  
			result = add32_with_carry(result>>32, result&0xffffffff);  
  
			/* 之前始按8字节对齐,可能有4字节剩下 */  
			if (len & 4) {  
				result += *(unsigned int *) buff;  
				buff += 4;  
			}  
		}  
  
	   /* 更早前按4字节对齐,可能有2字节剩下 */  
		if (len & 2) {  
			result += *(unsigned short *) buff;  
			buff += 2;  
		}  
	}  
  
	/* 最早之前按2字节对齐,可能有1字节剩下 */  
	if (len & 1)  
		result += *buff;  
  
	/* 再次带进位累加result的高32位和低32位 */  
	result = add32_with_carry(result>>32, result & 0xffffffff);   
  
	/* 这里涉及到一个技巧,用于处理初始地址为奇数的情况 */  
	if (unlikely(odd)) {  
		result = from32to16(result); /* 累加到result的低16位 */  
		/* result为:0 0 a b 
		 * 然后交换a和b,result变为:0 0 b a 
		 */  
		result = ((result >> 8) & 0xff) | ((result & oxff) << 8);  
	}  
  
	return result; /* 返回result的低32位 */  
}  
1
2
3
4
5
6
7
8
9
static inline unsigned short from32to16(unsigned a)  
{  
	unsigned short b = a >> 16;  
	asm ("addw %w2, %w0\n\t"  
			  "adcw $0, %w0\n"  
			  : "=r" (b)  
			  : "0" (b), "r" (a));  
	return b;  
}  

csum_partial_copy_from_user()用于拷贝用户空间数据到内核空间,同时计算用户数据的校验和,结果保存到skb->csum中(X86_64)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/** 
 * csum_partial_copy_from_user - Copy and checksum from user space. 
 * @src: source address (user space) 
 * @dst: destination address 
 * @len: number of bytes to be copied. 
 * @isum: initial sum that is added into the result (32bit unfolded) 
 * @errp: set to -EFAULT for an bad source address. 
 * 
 * Returns an 32bit unfolded checksum of the buffer. 
 * src and dst are best aligned to 64bits. 
 */  
  
unsigned int csum_partial_copy_from_user(const unsigned char __user *src,  
						unsigned char *dst, int len, unsigned int isum, int *errp)  
{  
	might_sleep();  
	*errp = 0;  
  
	if (likely(access_ok(VERIFY_READ, src, len))) {  
  
		/* Why 6, not 7? To handle odd addresses aligned we would need to do considerable 
		 * complications to fix the checksum which is defined as an 16bit accumulator. The fix 
		 * alignment code is primarily for performance compatibility with 32bit and that will handle 
		 * odd addresses slowly too. 
		 * 处理X010、X100、X110的起始地址。不处理X001,因为这会使复杂度大增加。 
		 */  
		if (unlikely((unsigned long)src & 6)) {  
			while (((unsigned long)src & 6) && len >= 2) {  
				__u16 val16;  
				*errp = __get_user(val16, (__u16 __user *)src);  
				if (*errp)  
					return isum;  
				*(__u16 *)dst = val16;  
				isum = add32_with_carry(isum, val16);  
				src += 2;  
				dst += 2;  
				len -= 2;  
			}  
		}  
  
		/* 计算函数是用纯汇编实现的,应该是因为效率吧 */  
		isum = csum_parial_copy_generic((__force void *)src, dst, len, isum, errp, NULL);  
  
		if (likely(*errp == 0))  
			return isum; /* 成功 */  
	}  
  
	*errp = -EFAULT;  
	memset(dst, 0, len);  
	return isum;  
}  

上述的实现比较复杂,来看下最简单的csum_partial_copy_from_user()实现(um)。

1
2
3
4
5
6
7
8
9
10
11
unsigned int csum_partial_copy_from_user(const unsigned char *src,  
						unsigned char *dst, int len, int sum,  
						int *err_ptr)  
{  
	if (copy_from_user(dst, src, len)) { /* 拷贝用户空间数据到内核空间 */  
		*err_ptr = -EFAULT; /* bad address */  
		return (-1);  
	}  
  
	return csum_partial(dst, len, sum); /* 计算用户数据的校验和,会存到skb->csum中 */  
}  

接收校验

@tcp_v4_rcv
/* 检查校验和 */
if (skb->ip_summed != CHECKSUM_UNNECESSARY && tcp_v4_checksum_init(skb))  
    goto bad_packet;   

接收校验的第一部分,主要是计算伪首部。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int tcp_v4_checksum_init(struct sk_buff *skb)  
{  
	/* 如果TCP报头、TCP数据的反码累加已经由硬件完成 */  
	if (skb->ip_summed == CHECKSUM_HW) {  
  
		/* 现在只需要再累加上伪首部,取反获取最终的校验和。 
		 * 校验和为0时,表示TCP数据报正确。 
		 */  
		if (! tcp_v4_check(skb->h.th, skb->len, skb->nh.iph->saddr, skb->nh.iph->daddr, skb->csum)) {  
			skb->ip_summed = CHECKSUM_UNNECESSARY;  
			return 0; /* 校验成功 */  
  
		} /* 没有else失败退出吗?*/  
	}  
  
	/* 对伪首部进行反码累加,主要用于软件方法 */  
	skb->csum = csum_tcpudp_nofold(skb->nh.iph->saddr, skb->nh.iph->daddr, skb->len, IPPROTO_TCP, 0);  
   
  
	/* 对于长度小于76字节的小包,接着累加TCP报头和报文,完成校验;否则,以后再完成检验。*/  
	if (skb->len <= 76) {  
		return __skb_checksum_complete(skb);  
	}  
}  

接收校验的第二部分,计算报头和报文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
tcp_v4_rcv、tcp_v4_do_rcv()

  | --> tcp_checksum_complete()

      | --> __tcp_checksum_complete()

          | --> __skb_checksum_complete()


tcp_rcv_established()

  | --> tcp_checksum_complete_user()

      | --> __tcp_checksum_complete_user()

          | --> __tcp_checksum_complete()

              | --> __skb_checksum_complete()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned int __skb_checksum_complete(struct sk_buff *skb)  
{  
	unsigned int sum;  
  
	sum = (u16) csum_fold(skb_checksum(skb, 0, skb->len, skb->csum));  
  
	if (likely(!sum)) { /* sum为0表示成功了 */  
		/* 硬件检测失败,软件检测成功了,说明硬件检测有误 */  
		if (unlikely(skb->ip_summed == CHECKSUM_HW))  
			netdev_rx_csum_fault(skb->dev);  
		skb->ip_summed = CHECKSUM_UNNECESSARY;  
	}  
	return sum;  
}  

计算skb包的校验和时,可以指定相对于skb->data的偏移量offset。由于skb包可能由分页和分段,所以需要考虑skb->data + offset是位于此skb段的线性区中、还是此skb的分页中,或者位于其它分段中。这个函数逻辑比较复杂。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/* Checksum skb data. */  
unsigned int skb_checksum(const struct sk_buff *skb, int offset, int len, unsigned int csum)  
{  
	int start = skb_headlen(skb); /* 线性区域长度 */  
	/* copy > 0,说明offset在线性区域中。 
	 * copy < 0,说明offset在此skb的分页数据中,或者在其它分段skb中。 
	 */  
	int i, copy = start - offset;  
	int pos = 0; /* 表示校验了多少数据 */  
  
	/* Checksum header. */  
	if (copy > 0) { /* 说明offset在本skb的线性区域中 */  
		if (copy > len)  
			copy = len; /* 不能超过指定的校验长度 */  
  
		/* 累加copy长度的线性区校验 */  
		csum = csum_partial(skb->data + offset, copy, csum);  
  
		if ((len -= copy) == 0)  
			return csum;  
  
		offset += copy; /* 接下来从这里继续处理 */  
		pos = copy; /* 已处理数据长 */  
	}  
  
	/* 累加本skb分页数据的校验和 */  
	for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {  
		int end;  
		BUG_TRAP(start <= offset + len);  
	  
		end = start + skb_shinfo(skb)->frags[i].size;  
  
		if ((copy = end - offset) > 0) { /* 如果offset位于本页中,或者线性区中 */  
			unsigned int csum2;  
			u8 *vaddr; /* 8位够吗?*/  
			skb_frag_t *frag = &skb_shinfo(skb)->frags[i];  
   
			if (copy > len)  
				copy = len;  
  
			vaddr = kmap_skb_frag(frag); /* 把物理页映射到内核空间 */  
			csum2 = csum_partial(vaddr + frag->page_offset + offset - start, copy, 0);  
			kunmap_skb_frag(vaddr); /* 解除映射 */  
  
			/* 如果pos为奇数,需要对csum2进行处理。 
			 * csum2:a, b, c, d => b, a, d, c 
			 */  
			csum = csum_block_add(csum, csum2, pos);  
  
			if (! (len -= copy))  
				return csum;  
  
			offset += copy;  
			pos += copy;  
		}  
		start = end; /* 接下来从这里处理 */  
	}  
   
	/* 如果此skb是个大包,还有其它分段 */  
	if (skb_shinfo(skb)->frag_list) {  
		struct sk_buff *list = skb_shinfo(skb)->frag_list;  
  
		for (; list; list = list->next) {  
			int end;  
			BUG_TRAP(start <= offset + len);  
   
			end = start + list->len;  
  
			if ((copy = end - offset) > 0) { /* 如果offset位于此skb分段中,或者分页,或者线性区 */  
				unsigned int csum2;  
				if (copy > len)  
					copy = len;  
  
				csum2 = skb_checksum(list, offset - start, copy, 0); /* 递归调用 */  
				csum = csum_block_add(csum, csum2, pos);  
				if ((len -= copy) == 0)  
					return csum;  
  
				offset += copy;  
				pos += copy;  
			}  
			start = end;  
		}  
	}  
  
	BUG_ON(len);  
	return csum;  
}

重算skb的checksum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <linux/version.h>
#include <linux/net.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <net/tcp.h>

void skbcsum(struct sk_buff *skb)
{
	struct tcphdr *tcph;
	struct iphdr *iph;
	int iphl;
	int tcphl;
	int tcplen;

	iph = (struct iphdr *)skb->data;
	iphl = iph->ihl << 2;
	tcph = (struct tcphdr *)(skb->data + iphl);
	tcphl = tcph->doff << 2;

	iph->check = 0;
	iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl);

	tcph->check    = 0;
	tcplen        = skb->len - (iph->ihl << 2);
	if (skb->ip_summed == CHECKSUM_PARTIAL) {
		tcph->check = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
				tcplen, IPPROTO_TCP, 0);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32)
		skb->csum = offsetof(struct tcphdr, check);
#else
		skb->csum_start    = skb_transport_header(skb) - skb->head;
		skb->csum_offset = offsetof(struct tcphdr, check);
#endif
	}
	else {
		skb->csum = 0;
		skb->csum = skb_checksum(skb, iph->ihl << 2, tcplen, 0);
		tcph->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
				tcplen, IPPROTO_TCP, skb->csum);

	}
}

Linux网络编程:原始套接字 SOCK_RAW, IPV6 rawsocket

https://stackoverflow.com/questions/31419727/how-to-send-modified-ipv6-packet-through-raw-socket

比较新的内核才支持

1
2
3
4
5
6
7
socket(fd, SOCK_RAW, IPPROTO_UDP); // 指定 TCP/UDP/ICMP

int val = 1;
ret = setsockopt(test->state.sockfd, IPPROTO_IPV6, IPV6_HDRINCL, &val, sizeof(val)); // 比较新的内核才支持

remote.sin6_port = 0; // 必须设置成0
sendto (socketFd, buffer, len, 0, (struct sockaddr *) &remote, sizeof(remote));

http://blog.chinaunix.net/uid-23069658-id-3280895.html

一、修改iphdr+tcphdr

对于TCP或UDP的程序开发,焦点在Data字段,我们没法直接对TCP或UDP头部字段进行赤裸裸的修改,当然还有IP头。换句话说,我们对它们头部操作的空间非常受限,只能使用它们已经开放给我们的诸如源、目的IP,源、目的端口等等。

原始套接字的创建方法:

1
socket(AF_INET, SOCK_RAW, protocol);

重点在protocol字段,这里就不能简单的将其值为0了。在头文件netinet/in.h中定义了系统中该字段目前能取的值,注意:有些系统中不一定实现了netinet/in.h中的所有协议。源代码的linux/in.h中和netinet/in.h中的内容一样。我们常见的有IPPROTO_TCP,IPPROTO_UDP和IPPROTO_ICMP。

用这种方式我就可以得到原始的IP包了,然后就可以自定义IP所承载的具体协议类型,如TCP,UDP或ICMP,并手动对每种承载在IP协议之上的报文进行填充。

先简单复习一下TCP报文的格式

原始套接字还提供了一个非常有用的参数IP_HDRINCL:

1、当开启该参数时:我们可以从IP报文首部第一个字节开始依次构造整个IP报文的所有选项,但是IP报文头部中的标识字段(设置为0时)和IP首部校验和字段总是由内核自己维护的,不需要我们关心。

2、如果不开启该参数:我们所构造的报文是从IP首部之后的第一个字节开始,IP首部由内核自己维护,首部中的协议字段被设置成调用socket()函数时我们所传递给它的第三个参数。

开启IP_HDRINCL特性的模板代码一般为:

1
2
3
4
const int on =1;
if (setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) {
	printf("setsockopt error!\n");
}

所以,我们还得复习一下IP报文的首部格式:

同样,我们重点关注IP首部中的着色部分区段的填充情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <linux/tcp.h>

#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <linux/sockios.h>

unsigned csum_tcpudp_nofold(unsigned saddr, unsigned daddr,
			unsigned len, unsigned proto, unsigned sum)
{
	unsigned long long s = (unsigned)sum;
	s += (unsigned)saddr;
	s += (unsigned)daddr;
	s += (proto + len) << 8;
	s += (s >> 32);
	return (unsigned)s;
}

unsigned short check_sum(unsigned short *addr, int len, unsigned sum)
{
	int nleft = len;
	unsigned short *w = addr;
	unsigned short ret = 0;
	while (nleft > 1) {
		sum += *w++;
		nleft -= 2;
	}
	if (nleft == 1) {
		*(unsigned char *)(&ret) = *(unsigned char *)w;
		sum += ret;
	}

	sum = (sum>>16) + (sum&0xffff);
	sum += (sum>>16);
	ret = ~sum;
	return ret;
}

//在该函数中构造整个IP报文,最后调用sendto函数将报文发送出去
void attack(int skfd, struct sockaddr_in *target, unsigned short srcport)
{
	char buf[256] = {0};
	struct ip *ip;
	struct tcphdr *tcp;
	int ip_len;
	int op_len = 12;

	//在我们TCP的报文中Data没有字段,所以整个IP报文的长度
	ip_len = sizeof(struct ip) + sizeof(struct tcphdr) + op_len;

	//开始填充IP首部
	ip=(struct ip*)buf;
	ip->ip_v = IPVERSION;
	ip->ip_hl = sizeof(struct ip)>>2;
	ip->ip_tos = 0;
	ip->ip_len = htons(ip_len);
	ip->ip_id = 0;
	ip->ip_off = 0;
	ip->ip_ttl = MAXTTL;
	ip->ip_p = IPPROTO_TCP;
	ip->ip_sum = 0;
	ip->ip_dst = target->sin_addr;

	//开始填充TCP首部
	tcp = (struct tcphdr*)(buf+sizeof(struct ip));
	tcp->source = htons(srcport);
	tcp->dest = target->sin_port;
	srand(time(NULL));
	tcp->doff = (sizeof(struct tcphdr) + op_len) >> 2; // tcphdr + option
	tcp->syn = 1;
	tcp->check = 0;
	tcp->window = ntohs(14600);

	int i = ip_len - op_len;
	// mss = 1460
	buf[i++] = 0x02;
	buf[i++] = 0x04;
	buf[i++] = 0x05;
	buf[i++] = 0xb4;
	// sack
	buf[i++] = 0x01;
	buf[i++] = 0x01;
	buf[i++] = 0x04;
	buf[i++] = 0x02;
	// wsscale = 7
	buf[i++] = 0x01;
	buf[i++] = 0x03;
	buf[i++] = 0x03;
	buf[i++] = 0x07;

	int T = 1;
	while(1) {
		if (T == 0) break;
		T--;
		tcp->seq = random();
		//源地址伪造,我们随便任意生成个地址,让服务器一直等待下去
		//ip->ip_src.s_addr = random();
		//自定义源地址192.168.204.136 = 0xc0a8cc88; 反转赋值
		ip->ip_src.s_addr = 0x88cca8c0;
		unsigned sum = csum_tcpudp_nofold(ip->ip_src.s_addr, ip->ip_dst.s_addr, sizeof(struct tcphdr)+op_len, IPPROTO_TCP, 0);
		tcp->check = check_sum((unsigned short*)tcp, sizeof(struct tcphdr)+op_len, sum);
//        ip->ip_sum = check_sum((unsigned short*)ip, sizeof(struct ip), 0);
		sendto(skfd, buf, ip_len, 0, (struct sockaddr*)target, sizeof(struct sockaddr_in));
	}
}

int main(int argc, char** argv)
{
	int skfd;
	struct sockaddr_in target;
	struct hostent *host;
	const int on = 1;
	unsigned short srcport;

	if (argc != 4) {
		printf("Usage:%s dstip dstport srcport\n", argv[0]);
		exit(1);
	}

	bzero(&target, sizeof(struct sockaddr_in));
	target.sin_family = AF_INET;
	target.sin_port = htons(atoi(argv[2]));

	if (inet_aton(argv[1], &target.sin_addr) == 0) {
		host = gethostbyname(argv[1]);
		if(host == NULL) {
			printf("TargetName Error:%s\n", hstrerror(h_errno));
			exit(1);
		}
		target.sin_addr = *(struct in_addr *)(host->h_addr_list[0]);
	}

	//将协议字段置为IPPROTO_TCP,来创建一个TCP的原始套接字
	if (0 > (skfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP))) {
		perror("Create Error");
		exit(1);
	}

	//用模板代码来开启IP_HDRINCL特性,我们完全自己手动构造IP报文
	if (0 > setsockopt(skfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on))) {
		perror("IP_HDRINCL failed");
		exit(1);
	}

	//因为只有root用户才可以play with raw socket :)
	setuid(getpid());
	srcport = atoi(argv[3]);
	attack(skfd, &target, srcport);
}
  • 原始套接字上也可以调用connet、bind之类的函数

修改mac+iphdr+tcphdr

blog.chinaunix.net/uid-23069658-id-3283534.html

在Linux系统中要从链路层(MAC)直接收发数帧,比较普遍的做法就是用libpcap和libnet两个动态库来实现。但今天我们就要用原始套接字来实现这个功能。

这里的2字节帧类型用来指示该数据帧所承载的上层协议是IP、ARP或其他。

为了实现直接从链路层收发数据帧,我们要用到原始套接字的如下形式:

1
socket(PF_PACKET, type, protocol)

1、其中type字段可取SOCK_RAW或SOCK_DGRAM。它们两个都使用一种与设备无关的标准物理层地址结构struct sockaddr_ll{},但具体操作的报文格式不同:

SOCK_RAW:直接向网络硬件驱动程序发送(或从网络硬件驱动程序接收)没有任何处理的完整数据报文(包括物理帧的帧头),这就要求我们必须了解对应设备的物理帧帧头结构,才能正确地装载和分析报文。也就是说我们用这种套接字从网卡驱动上收上来的报文包含了MAC头部,如果我们要用这种形式的套接字直接向网卡发送数据帧,那么我们必须自己组装我们MAC头部。这正符合我们的需求。

SOCK_DGRAM:这种类型的套接字对于收到的数据报文的物理帧帧头会被系统自动去掉,然后再将其往协议栈上层传递;同样地,在发送时数据时,系统将会根据sockaddr_ll结构中的目的地址信息为数据报文添加一个合适的MAC帧头。

2、protocol字段,常见的,一般情况下该字段取ETH_P_IP,ETH_P_ARP,ETH_P_RARP或ETH_P_ALL,当然链路层协议很多,肯定不止我们说的这几个,但我们一般只关心这几个就够我们用了。这里简单提一下网络数据收发的一点基础。协议栈在组织数据收发流程时需要处理好两个方面的问题:“从上倒下”,即数据发送的任务;“从下到上”,即数据接收的任务。数据发送相对接收来说要容易些,因为对于数据接收而言,网卡驱动还要明确什么样的数据该接收、什么样的不该接收等问题。protocol字段可选的四个值及其意义如下:

protocol 值 作用
ETH_P_IP 0X0800 只接收发往目的MAC是本机的IP类型的数据帧
ETH_P_ARP 0X0806 只接收发往目的MAC是本机的ARP类型的数据帧
ETH_P_RARP 0X8035 只接受发往目的MAC是本机的RARP类型的数据帧
ETH_P_ALL 0X0003 接收发往目的MAC是本机的所有类型(ip,arp,rarp)的数据帧,同时还可以接收从本机发出去的所有数据帧。在混杂模式打开的情况下,还会接收到发往目的MAC为非本地硬件地址的数据帧。

protocol字段可取的所有协议参见/usr/include/linux/if_ether.h头文件里的定义。

最后,格外需要留心一点的就是,发送数据的时候需要自己组织整个以太网数据帧。和地址相关的结构体就不能再用前面的struct sockaddr_in{}了,而是struct sockaddr_ll{},如下:

1
2
3
4
5
6
7
8
9
struct sockaddr_ll{
	unsigned short sll_family; /* 总是 AF_PACKET */
	unsigned short sll_protocol; /* 物理层的协议 */
	int sll_ifindex; /* 接口号 */
	unsigned short sll_hatype; /* 报头类型 */
	unsigned char sll_pkttype; /* 分组类型 */
	unsigned char sll_halen; /* 地址长度 */
	unsigned char sll_addr[8]; /* 物理层地址 */
};

sll_protocoll:取值在linux/if_ether.h中,可以指定我们所感兴趣的二层协议;

sll_ifindex:置为0表示处理所有接口,对于单网卡的机器就不存在“所有”的概念了。如果你有多网卡,该字段的值一般通过ioctl来搞定,模板代码如下,如果我们要获取eth0接口的序号,可以使用如下代码来获取:

1
2
3
4
5
6
struct  sockaddr_ll  sll;
struct ifreq ifr;

strcpy(ifr.ifr_name, "eth0");
ioctl(sockfd, SIOCGIFINDEX, &ifr);
sll.sll_ifindex = ifr.ifr_ifindex;

sll_hatype:ARP硬件地址类型,定义在 linux/if_arp.h 中。 取ARPHRD_ETHER时表示为以太网。

sll_pkttype:包含分组类型。目前,有效的分组类型有:目标地址是本地主机的分组用的 PACKET_HOST,物理层广播分组用的 PACKET_BROADCAST ,发送到一个物理层多路广播地址的分组用的 PACKET_MULTICAST,在混杂(promiscuous)模式下的设备驱动器发向其他主机的分组用的 PACKET_OTHERHOST,源于本地主机的分组被环回到分组套接口用的 PACKET_OUTGOING。这些类型只对接收到的分组有意义。

sll_addr和sll_halen指示物理层(如以太网,802.3,802.4或802.5等)地址及其长度,严格依赖于具体的硬件设备。类似于获取接口索引sll_ifindex,要获取接口的物理地址,可以采用如下代码:

1
2
3
4
struct ifreq ifr;

strcpy(ifr.ifr_name, "eth0");
ioctl(sockfd, SIOCGIFHWADDR, &ifr);

缺省情况下,从任何接口收到的符合指定协议的所有数据报文都会被传送到原始PACKET套接字口,而使用bind系统调用并以一个sochddr_ll结构体对象将PACKET套接字与某个网络接口相绑定,就可使我们的PACKET原始套接字只接收指定接口的数据报文。

接下来我们简单介绍一下网卡是怎么收报的,如果你对这部分已经很了解可以跳过这部分内容。网卡从线路上收到信号流,网卡的驱动程序会去检查数据帧开始的前6个字节,即目的主机的MAC地址,如果和自己的网卡地址一致它才会接收这个帧,不符合的一般都是直接无视。然后该数据帧会被网络驱动程序分解,IP报文将通过网络协议栈,最后传送到应用程序那里。往上层传递的过程就是一个校验和“剥头”的过程,由协议栈各层去实现。

接下来我们来写个简单的抓包程序,将那些发给本机的IPv4报文全打印出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/if_ether.h>

int main(int argc, char **argv)
{
	int sock, n;
	char buffer[2048];
	struct ethhdr *eth;
	struct iphdr *iph;

	if (0 > (sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)))) {
		perror("socket");
		exit(1);
	}

	while (1) {
		printf("=====================================\n");
		//注意:在这之前我没有调用bind函数,原因是什么呢?
		n = recvfrom(sock, buffer, 2048, 0, NULL, NULL);
		printf("%d bytes read\n", n);

		//接收到的数据帧头6字节是目的MAC地址,紧接着6字节是源MAC地址。
		eth = (struct ethhdr*)buffer;
		printf("Dest MAC addr:%02x:%02x:%02x:%02x:%02x:%02x\n",eth->h_dest[0],eth->h_dest[1],eth->h_dest[2],eth->h_dest[3],eth->h_dest[4],eth->h_dest[5]);
		printf("Source MAC addr:%02x:%02x:%02x:%02x:%02x:%02x\n",eth->h_source[0],eth->h_source[1],eth->h_source[2],eth->h_source[3],eth->h_source[4],eth->h_source[5]);

		iph = (struct iphdr*)(buffer + sizeof(struct ethhdr));
		//我们只对IPV4且没有选项字段的IPv4报文感兴趣
		if(iph->version == 4 && iph->ihl == 5){
			unsigned char *sd, *dd;
			sd = (unsigned char*)&iph->saddr;
			dd = (unsigned char*)&iph->daddr;
			printf("Source Host: %d.%d.%d.%d Dest host: %d.%d.%d.%d\n", sd[0], sd[1], sd[2], sd[3], dd[0], dd[1], dd[2], dd[3]);
		//    printf("Source host:%s\n", inet_ntoa(iph->saddr));
		//    printf("Dest host:%s\n", inet_ntoa(iph->daddr));
		}
	}
	return 0;
}

构造mac源地址包,注意目标mac地址要正确,可以本机先抓包看看是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <linux/tcp.h>

#include <linux/if_ether.h>
#include <linux/if_arp.h>
#include <linux/sockios.h>

unsigned csum_tcpudp_nofold(unsigned saddr, unsigned daddr,
			unsigned len, unsigned proto, unsigned sum)
{
	unsigned long long s = (unsigned)sum;
	s += (unsigned)saddr;
	s += (unsigned)daddr;
	s += (proto + len) << 8;
	s += (s >> 32);
	return (unsigned)s;
}

unsigned short check_sum(unsigned short *addr, int len, unsigned sum)
{
	int nleft = len;
	unsigned short *w = addr;
	unsigned short ret = 0;
	while (nleft > 1) {
		sum += *w++;
		nleft -= 2;
	}
	if (nleft == 1) {
		*(unsigned char *)(&ret) = *(unsigned char *)w;
		sum += ret;
	}

	sum = (sum>>16) + (sum&0xffff);
	sum += (sum>>16);
	ret = ~sum;
	return ret;
}

int change(char c)
{
	if (c >= 'a') return c-'a'+10;
	if (c >= 'A') return c-'A'+10;
	return c-'0';
}

//在该函数中构造整个IP报文,最后调用sendto函数将报文发送出去
void attack(int skfd, struct sockaddr_ll *target, char **argv)
{
	char buf[512]={0};
	struct ethhdr *eth;
	struct ip *ip;
	struct tcphdr *tcp;
	int pks_len;
	int i;
	int op_len = 12;
	unsigned short dstport;
	dstport = atoi(argv[3]);

	//在我们TCP的报文中Data没有字段,所以整个IP报文的长度
	pks_len = sizeof(struct ethhdr) + sizeof(struct ip) + sizeof(struct tcphdr) + op_len;
	eth = (struct ethhdr *) buf;
	/*
	eth->h_dest[0] = 0x00;
	eth->h_dest[1] = 0x50;
	eth->h_dest[2] = 0x56;
	eth->h_dest[3] = 0xee;
	eth->h_dest[4] = 0x14;
	eth->h_dest[5] = 0xa6;
	*/

	for (i=0;i<6;i++)
		eth->h_dest[i] = change(argv[1][i*3])*16 + change(argv[1][i*3+1]);

	/*
	eth->h_source[0] = 0x00;
	eth->h_source[1] = 0x0b;
	eth->h_source[2] = 0x28;
	eth->h_source[3] = 0xd7;
	eth->h_source[4] = 0x26;
	eth->h_source[5] = 0xa6;
	*/
	eth->h_proto = ntohs(ETH_P_IP);

	//开始填充IP首部
	ip=(struct ip*)(buf + sizeof(struct ethhdr));
	ip->ip_v = IPVERSION;
	ip->ip_hl = sizeof(struct ip) >> 2;
	ip->ip_tos = 0;
	ip->ip_len = htons(pks_len - sizeof(struct ethhdr));
	ip->ip_id = 0;
	ip->ip_off = 0;
	ip->ip_ttl = MAXTTL;
	ip->ip_p = IPPROTO_TCP;
	ip->ip_sum = 0;
	ip->ip_dst.s_addr = inet_addr(argv[2]);

	//开始填充TCP首部
	srand(time(NULL));
	tcp = (struct tcphdr*)(buf + sizeof(struct ethhdr) + sizeof(struct ip));
	tcp->source = random()%50000+10000;
	tcp->dest = ntohs(dstport);
	tcp->seq = random();
	tcp->doff = (sizeof(struct tcphdr) + op_len) >> 2;
	tcp->syn = 1;
	tcp->check = 0;
	tcp->window = ntohs(14600);

	i = pks_len - op_len;
	// mss = 1460
	buf[i++] = 0x02;
	buf[i++] = 0x04;
	buf[i++] = 0x05;
	buf[i++] = 0xb4;
	// sack
	buf[i++] = 0x01;
	buf[i++] = 0x01;
	buf[i++] = 0x04;
	buf[i++] = 0x02;
	// wsscale = 7
	buf[i++] = 0x01;
	buf[i++] = 0x03;
	buf[i++] = 0x03;
	buf[i++] = 0x07;

	int T = 1;
	while(1) {
		if (T == 0) break;
		T--;
		//源地址伪造,我们随便任意生成个地址,让服务器一直等待下去
		ip->ip_src.s_addr = random();
		//自定义源地址192.168.204.136 => 0xc0a8cc88
		//ip->ip_src.s_addr = 0x8fcca8c0;
		unsigned sum = csum_tcpudp_nofold(ip->ip_src.s_addr, ip->ip_dst.s_addr, sizeof(struct tcphdr)+op_len, IPPROTO_TCP, 0);
		tcp->check = check_sum((unsigned short*)tcp, sizeof(struct tcphdr)+op_len, sum);
		ip->ip_sum = check_sum((unsigned short*)ip, sizeof(struct ip), 0);
		sendto(skfd, buf, pks_len, 0, (struct sockaddr*)target, sizeof(struct sockaddr_ll));
	}
}

int main(int argc, char** argv)
{
	int skfd;
	struct sockaddr_ll target;
	struct hostent *host;
	const int on=1;

	if (argc != 4) {
		printf("Usage:%s dstmac dstip dstport\n", argv[0]);
		exit(1);
	}
	if (strlen(argv[1]) != 17) {
		printf("Usage: dstmac must be xx:xx:xx:xx:xx:xx\n");
		exit(1);
	}

	//将协议字段置为IPPROTO_TCP,来创建一个TCP的原始套接字
	if (0 > (skfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP)))) {
		perror("Create Error");
		exit(1);
	}

	// mac
	bzero(&target, sizeof(struct sockaddr_ll));

	struct ifreq ifr;
	strncpy(ifr.ifr_name, "eth0", IFNAMSIZ);
	ioctl(skfd, SIOCGIFINDEX, &ifr);
	target.sll_ifindex = ifr.ifr_ifindex;
	/*
	target.sll_family = AF_PACKET;
	target.sll_protocol = ntohs(80);
	target.sll_hatype = ARPHRD_ETHER;
	target.sll_pkttype = PACKET_OTHERHOST;
	target.sll_halen = ETH_ALEN;
	memset(target.sll_addr,0,8);
	target.sll_addr[0] = 0x00;
	target.sll_addr[1] = 0x0C;
	target.sll_addr[2] = 0x29;
	target.sll_addr[3] = 0x61;
	target.sll_addr[4] = 0xB6;
	target.sll_addr[5] = 0x43;
	*/


	/*
	//http://blog.chinaunix.net/uid-305141-id-2133781.html
	struct sockaddr_ll sll;
	struct ifreq ifstruct;
	memset (&sll, 0, sizeof (sll));
	sll.sll_family = PF_PACKET;
	sll.sll_protocol = htons (ETH_P_IP);

	strcpy (ifstruct.ifr_name, "eth0");
	ioctl (skfd, SIOCGIFINDEX, &ifstruct);
	sll.sll_ifindex = ifstruct.ifr_ifindex;

	strcpy (ifstruct.ifr_name, "eth0");
	ioctl (skfd, SIOCGIFHWADDR, &ifstruct);
	memcpy (sll.sll_addr, ifstruct.ifr_ifru.ifru_hwaddr.sa_data, ETH_ALEN);
	sll.sll_halen = ETH_ALEN;

	if (bind (skfd, (struct sockaddr *) &sll, sizeof (sll)) == -1) {
		printf ("bind:   ERROR\n");
		return -1;
	}

	memset(&ifstruct, 0, sizeof(ifstruct));
	strcpy (ifstruct.ifr_name, "eth0");
	if (ioctl (skfd, SIOCGIFFLAGS, &ifstruct) == -1) {
		perror ("iotcl()\n");
		printf ("Fun:%s Line:%d\n", __func__, __LINE__);
		return -1;
	}

	ifstruct.ifr_flags |= IFF_PROMISC;

	if(ioctl(skfd, SIOCSIFFLAGS, &ifstruct) == -1) {
		perror("iotcl()\n");
		printf ("Fun:%s Line:%d\n", __func__, __LINE__);
		return -1;
	} 
*/
	//因为只有root用户才可以play with raw socket :)
	setuid(getpid());
//    attack(skfd, &sll, srcport);
	attack(skfd, &target, argv);
}