kk Blog —— 通用基础


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

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);
}

SSH端口转发 及 SSH代理,socks vpn

SSH端口转发

假设A、B为内网机,C为可登录公网机。那么A连B过程为:

假设 A、B、C 的ssh端口分别为portA、portB、portC。

1. 远程转发-R, 远程机转发到本地机

在被连的B上先运行如下命令

1
ssh -p portC -f -N -R 9000:localhost:portB userC@hostC-IP

这样到C机上9000端口的连接会被转发到B机的portB上。

2.本地转发-L, 本地机转发到远程机

在发起连接的A上运行如下命令

1
ssh -p portC -f -N -L 9999:localhost:9000 userC@hostC-IP

这样到A机9999端口的连接会被转发到C机的9000上。而C的9000又会被转发到B的portB上。
所以只要在A上运行:

1
ssh -p 9999 userB@127.0.0.1

就相当于ssh到了B机的portB上。

参数
1
2
3
4
f  表示后台用户验证,这个选项很有用,没有shell的不可登陆账号也能使用.
N 表示不执行脚本或命令
-L 本地转发
-R 远程转发
路由器设置省去C机

在路由器添加转发规则,端口为A机ssh监听端口,ip为A机内网IP。记下路由器公网IP。
然后只要在B机上直接连路由器公网IP+转发端口

1
ssh -p portA -f -N -R 9999:localhost:portB userA@routeA-IP

然后在A机上直接

1
ssh -p 9999 userB@127.0.0.1

A就能连上B

SSH代理–网站限制内网IP?代理一下就好了

1 远程机有公网IP

只要在本地运行

1
ssh -p port -qTfnN -D 7070 user@sshserver

然后在firefox的 首选项->高级->网络->设置 里面改成手动配置代理,只填"SOCKS"这一行即可。

2 若远程机为内网机

先按前面端口转发的方法,在本机映射一个到远程机的端口localport,然后命令改成

1
ssh -p localport -qTfnN -D 7070 user@127.0.0.1

这样firefox下要填127.0.0.1和7070

Linux TCP数据包接收处理 tcp_recvmsg

http://blog.csdn.net/mrpre/article/details/33347221

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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
/* 
 *    This routine copies from a sock struct into the user buffer.
 *
 *    Technical note: in 2.3 we work on _locked_ socket, so that
 *    tricks with *seq access order and skb->users are not required.
 *    Probably, code can be easily improved even more.
 */

int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
		size_t len, int nonblock, int flags, int *addr_len)
{
	struct tcp_sock *tp = tcp_sk(sk);
	int copied = 0;
	u32 peek_seq;
	u32 *seq;
	unsigned long used;
	int err;
	int target;    /* Read at least this many bytes */
	long timeo;
	struct task_struct *user_recv = NULL;
	int copied_early = 0;
	struct sk_buff *skb;
	u32 urg_hole = 0;

	//功能:“锁住sk”,并非真正的加锁,而是执行sk->sk_lock.owned = 1 
	//目的:这样软中断上下文能够通过owned ,判断该sk是否处于进程上下文。
	//提供一种同步机制。
	lock_sock(sk);

	TCP_CHECK_TIMER(sk);

	err = -ENOTCONN;
	if (sk->sk_state == TCP_LISTEN)
		goto out;

	//获取延迟,如果用户设置为非阻塞,那么timeo ==0000 0000 0000 0000
	//如果用户使用默认recv系统调用
	//则为阻塞,此时timeo ==0111 1111 1111 1111
	//timeo 就2个值
	timeo = sock_rcvtimeo(sk, nonblock);

	/* Urgent data needs to be handled specially. */
	if (flags & MSG_OOB)
		goto recv_urg;

	//待拷贝的下一个序列号
	seq = &tp->copied_seq;

	//设置了MSG_PEEK,表示不让数据从缓冲区移除,目的是下一次调用recv函数
	//仍然能够读到相同数据
	if (flags & MSG_PEEK) {
		peek_seq = tp->copied_seq;
		seq = &peek_seq;
	}

	//如果设置了MSG_WAITALL,则target  ==len,即recv函数中的参数len
	//如果没设置MSG_WAITALL,则target  == 1
	target = sock_rcvlowat(sk, flags & MSG_WAITALL, len);

	//大循环
	do {
		u32 offset;

		/* Are we at urgent data? Stop if we have read anything or have SIGURG pending. */
		if (tp->urg_data && tp->urg_seq == *seq) {
			if (copied)
				break;
			if (signal_pending(current)) {
				copied = timeo ? sock_intr_errno(timeo) : -EAGAIN;
				break;
			}
		}

		/* Next get a buffer. */

		//小循环
		skb_queue_walk(&sk->sk_receive_queue, skb) {
			/* Now that we have two receive queues this
				* shouldn't happen.
				*/
			if (WARN(before(*seq, TCP_SKB_CB(skb)->seq),
								KERN_INFO "recvmsg bug: copied %X "
									      "seq %X rcvnxt %X fl %X\n", *seq,
									      TCP_SKB_CB(skb)->seq, tp->rcv_nxt,
									      flags))
				break;

			//如果用户的缓冲区(即用户malloc的buf)长度够大,offset一般是0。
			//即 “下次准备拷贝数据的序列号”==此时获取报文的起始序列号
			//什么情况下offset >0呢?很简答,如果用户缓冲区12字节,而这个skb有120字节
			//那么一次recv系统调用,只能获取skb中的前12个字节,下一次执行recv系统调用
			//offset就是12了,offset表示从第12个字节开始读取数据,前12个字节已经读取了。
			//那这个"已经读取12字节"这个消息,存在哪呢?
			//在*seq = &tp->copied_seq;中
			offset = *seq - TCP_SKB_CB(skb)->seq;
			if (tcp_hdr(skb)->syn)
				offset--;
			if (offset < skb->len)
				goto found_ok_skb;
			if (tcp_hdr(skb)->fin)
				goto found_fin_ok;
			WARN(!(flags & MSG_PEEK), KERN_INFO "recvmsg bug 2: "
					"copied %X seq %X rcvnxt %X fl %X\n",
					*seq, TCP_SKB_CB(skb)->seq,
					tp->rcv_nxt, flags);
		}

		//执行到了这里,表明小循环中break了,既然break了,说明sk_receive_queue中
		//已经没有skb可以读取了
		//如果没有执行到这里说明前面的小循环中执行了goto,读到有用的skb,或者读到fin都会goto。
		//没有skb可以读取,说明什么?
		//可能性1:当用户第一次调用recv时,压根没有数据到来
		//可能性2:skb->len一共20字节,假设用户调用一次 recv,读取12字节,再调用recv,
		//读取12字节,此时skb由于上次已经被读取了12字节,只剩下8字节。
		//于是代码的逻辑上,再会要求获取skb,来读取剩下的8字节。

		//可能性1的情况下,copied == 0,肯定不会进这个if。后续将执行休眠
		//可能性2的情况下,情况比较复杂。可能性2表明数据没有读够用户想要的len长度
		//虽然进程上下文中,没有读够数据,但是可能我们在读数据的时候
		//软中断把数据放到backlog队列中了,而backlog对队列中的数据或许恰好让我们读够数
		//据。

		//copied了数据的,copied肯定>=1,而target 是1或者len
		//copied只能取0(可能性1),或者0~len(可能性2)
		//copied >= target 表示我们取得我们想要的数据了,何必进行休眠,直接return
		//如果copied 没有达到我们想要的数据,则看看sk_backlog是否为空
		//空的话,尽力了,只能尝试休眠
		//非空的话,还有一线希望,我们去sk_backlog找找数据,看看是否能够达到我们想要的
		//数据大小

		//我觉得copied == target是会出现的,但是出现的话,也不会进现在这个流程
		//,如有不对,请各位大神指正,告诉我
		//说明情况下copied == target

		/* Well, if we have backlog, try to process it now yet. */
		if (copied >= target && !sk->sk_backlog.tail)
			break;


		if (copied) {
			//可能性2,拷贝了数据,但是没有拷贝到指定大小
			if (sk->sk_err ||
							sk->sk_state == TCP_CLOSE ||
							(sk->sk_shutdown & RCV_SHUTDOWN) ||
							!timeo ||
							signal_pending(current))
				break;
		} else {
			//可能性1
			if (sock_flag(sk, SOCK_DONE))
				break;

			if (sk->sk_err) {
				copied = sock_error(sk);
				break;
			}

			if (sk->sk_shutdown & RCV_SHUTDOWN)
				break;

			if (sk->sk_state == TCP_CLOSE) {
				if (!sock_flag(sk, SOCK_DONE)) {
					/* This occurs when user tries to read
						* from never connected socket.
						*/
					copied = -ENOTCONN;
					break;
				}
				break;
			}

			//是否是阻塞的,不是,就return了。
			if (!timeo) {
				copied = -EAGAIN;
				break;
			}

			if (signal_pending(current)) {
				copied = sock_intr_errno(timeo);
				break;
			}
		}

		tcp_cleanup_rbuf(sk, copied);

		//sysctl_tcp_low_latency 默认0tp->ucopy.task == user_recv肯定也成立

		if (!sysctl_tcp_low_latency && tp->ucopy.task == user_recv) {
			/* Install new reader */
			if (!user_recv && !(flags & (MSG_TRUNC | MSG_PEEK))) {
				user_recv = current;
				tp->ucopy.task = user_recv;
				tp->ucopy.iov = msg->msg_iov;
			}

			tp->ucopy.len = len;

			WARN_ON(tp->copied_seq != tp->rcv_nxt &&
				!(flags & (MSG_PEEK | MSG_TRUNC)));

			/* Ugly... If prequeue is not empty, we have to
				* process it before releasing socket, otherwise
				* order will be broken at second iteration.
				* More elegant solution is required!!!
				*
				* Look: we have the following (pseudo)queues:
				*
				* 1. packets in flight
				* 2. backlog
				* 3. prequeue
				* 4. receive_queue
				*
				* Each queue can be processed only if the next ones
				* are empty. At this point we have empty receive_queue.
				* But prequeue _can_ be not empty after 2nd iteration,
				* when we jumped to start of loop because backlog
				* processing added something to receive_queue.
				* We cannot release_sock(), because backlog contains
				* packets arrived _after_ prequeued ones.
				*
				* Shortly, algorithm is clear --- to process all
				* the queues in order. We could make it more directly,
				* requeueing packets from backlog to prequeue, if
				* is not empty. It is more elegant, but eats cycles,
				* unfortunately.
				*/

			if (!skb_queue_empty(&tp->ucopy.prequeue))
				goto do_prequeue;

			/* __ Set realtime policy in scheduler __ */
		}

		if (copied >= target) {
			/* Do not sleep, just process backlog. */
			release_sock(sk);
			lock_sock(sk);
		} else
					sk_wait_data(sk, &timeo); 
		//在此处睡眠了,将在tcp_prequeue函数中调用wake_up_interruptible_poll唤醒
		
		//软中断会判断用户是正在读取检查并且睡眠了,如果是的话,就直接把数据拷贝
		//到prequeue队列,然后唤醒睡眠的进程。因为进程睡眠,表示没有读到想要的字节数
		//此时,软中断有数据到来,直接给进程,这样进程就能以最快的速度被唤醒。


		if (user_recv) {
			int chunk;

			/* __ Restore normal policy in scheduler __ */

			if ((chunk = len - tp->ucopy.len) != 0) {
				NET_ADD_STATS_USER(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMBACKLOG, chunk);
				len -= chunk;
				copied += chunk;
			}

			if (tp->rcv_nxt == tp->copied_seq &&
							!skb_queue_empty(&tp->ucopy.prequeue)) {
do_prequeue:
				tcp_prequeue_process(sk);

				if ((chunk = len - tp->ucopy.len) != 0) {
					NET_ADD_STATS_USER(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);
					len -= chunk;
					copied += chunk;
				}
			}
		}
		if ((flags & MSG_PEEK) &&
						(peek_seq - copied - urg_hole != tp->copied_seq)) {
			if (net_ratelimit())
				printk(KERN_DEBUG "TCP(%s:%d): Application bug, race in MSG_PEEK.\n",
									      current->comm, task_pid_nr(current));
			peek_seq = tp->copied_seq;
		}
		continue;

	found_ok_skb:
		/* Ok so how much can we use? */
		//skb中还有多少聚聚没有拷贝。
		//正如前面所说的,offset是上次已经拷贝了的,这次从offset开始接下去拷贝
				used = skb->len - offset;
		//很有可能used的大小,即skb剩余长度,依然大于用户的缓冲区大小(len)。所以依然
		//只能拷贝len长度。一般来说,用户还得执行一次recv系统调用。直到skb中的数据读完
		if (len < used)
			used = len;

		/* Do we have urgent data here? */
		if (tp->urg_data) {
			u32 urg_offset = tp->urg_seq - *seq;
			if (urg_offset < used) {
				if (!urg_offset) {
					if (!sock_flag(sk, SOCK_URGINLINE)) {
						++*seq;
						urg_hole++;
						offset++;
						used--;
						if (!used)
							goto skip_copy;
					}
				} else
					used = urg_offset;
			}
		}

		if (!(flags & MSG_TRUNC)) {
			{
				//一般都会进这个if,进行数据的拷贝,把能够读到的数据,放到用户的缓冲区
				err = skb_copy_datagram_iovec(skb, offset,
						msg->msg_iov, used);
				if (err) {
					/* Exception. Bailout! */
					if (!copied)
						copied = -EFAULT;
					break;
				}
			}
		}

		//更新标志位,seq 是指针,指向了tp->copied_seq
		//used是我们有能力拷贝的数据大小,即已经拷贝到用户缓冲区的大小
		//正如前面所说,如果用户的缓冲区很小,一次recv拷贝不玩skb中的数据,
		//我们需要保存已经拷贝了的大小,下次recv时,从这个大小处继续拷贝。
		//所以需要更新copied_seq。
		*seq += used;
		copied += used;
		len -= used;

		tcp_rcv_space_adjust(sk);

skip_copy:
		if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) {
			tp->urg_data = 0;
			tcp_fast_path_check(sk);
		}

		//这个就是判断我们是否拷贝完了skb中的数据,如果没有continue
		//这种情况下,len经过 len -= used; ,已经变成0,所以continue的效果相当于
		//退出了这个大循环。可以理解,你只能拷贝len长度,拷贝完之后,那就return了。

		//还有一种情况used + offset ==  skb->len,表示skb拷贝完了。这时我们只需要释放skb
		//下面会讲到
		if (used + offset < skb->len)
			continue;

		//看看这个数据报文是否含有fin,含有fin,则goto到found_fin_ok
		if (tcp_hdr(skb)->fin)
			goto found_fin_ok;

		//执行到这里,标明used + offset ==  skb->len,报文也拷贝完了,那就把skb摘链释放
		if (!(flags & MSG_PEEK)) {
			sk_eat_skb(sk, skb, copied_early);
			copied_early = 0;
		}
		//这个cintinue不一定是退出大循环,可能还会执行循环。
		//假设用户设置缓冲区12字节,你skb->len长度20字节。
		//第一次recv读取了12字节,skb剩下8,下一次调用recv再想读取12,
		//但是只能读取到这8字节了。
		//此时len 变量长度为4,那么这个continue依旧在这个循环中,
		//函数还是再次从do开始,使用skb_queue_walk,找skb
		//如果sk_receive_queue中skb仍旧有,那么继续读,直到len == 0
		//如果没有skb了,我们怎么办?我们的len还有4字节怎么办?
		//这得看用户设置的recv函数阻塞与否,即和timeo变量相关了。
		continue;

	found_fin_ok:
		/* Process the FIN. */
		++*seq;
		if (!(flags & MSG_PEEK)) {
			//把skb从sk_receive_queue中摘链
			sk_eat_skb(sk, skb, copied_early);
			copied_early = 0;
		}
		break;
	} while (len > 0);

	//到这里是大循环退出
	//休眠过的进程,然后退出大循环 ,才满足 if (user_recv) 条件
	if (user_recv) {
		if (!skb_queue_empty(&tp->ucopy.prequeue)) {
			int chunk;

			tp->ucopy.len = copied > 0 ? len : 0;

			tcp_prequeue_process(sk);

			if (copied > 0 && (chunk = len - tp->ucopy.len) != 0) {
				NET_ADD_STATS_USER(sock_net(sk), LINUX_MIB_TCPDIRECTCOPYFROMPREQUEUE, chunk);
				len -= chunk;
				copied += chunk;
			}
		}

		//数据读取完毕,清零
		tp->ucopy.task = NULL;
		tp->ucopy.len = 0;
	}

	/* According to UNIX98, msg_name/msg_namelen are ignored
		* on connected socket. I was just happy when found this 8) --ANK
		*/

	/* Clean up data we have read: This will do ACK frames. */
	//很重要,将更新缓存,并且适当的时候发送ack
	tcp_cleanup_rbuf(sk, copied);

	TCP_CHECK_TIMER(sk);
	release_sock(sk);
	return copied;

out:
	TCP_CHECK_TIMER(sk);
	release_sock(sk);
	return err;

recv_urg:
	err = tcp_recv_urg(sk, msg, len, flags);
	goto out;
}

Linux TCP数据包接收处理 tcp_v4_rcv

http://blog.sina.com.cn/s/blog_52355d840100b6sd.html

tcp_v4_rcv函数

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
int tcp_v4_rcv(struct sk_buff *skb)
{
	const struct iphdr *iph;
	struct tcphdr *th;
	struct sock *sk;
	int ret;
	  
	//如果不是发往本地的数据包,则直接丢弃
	if (skb->pkt_type != PACKET_HOST)
		goto discard_it;

	TCP_INC_STATS_BH(TCP_MIB_INSEGS);

	//包长是否大于TCP头的长度
	if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
		goto discard_it;

	//取得TCP首部
	th = tcp_hdr(skb);

	//检查TCP首部的长度和TCP首部中的doff字段是否匹配
	if (th->doff < sizeof(struct tcphdr) / 4)
		goto bad_packet;

	//检查TCP首部到TCP数据之间的偏移是否越界
	if (!pskb_may_pull(skb, th->doff * 4))
		goto discard_it;

	if (!skb_csum_unnecessary(skb) && tcp_v4_checksum_init(skb))
		goto bad_packet;

	 th = tcp_hdr(skb);
	iph = ip_hdr(skb);
	TCP_SKB_CB(skb)->seq = ntohl(th->seq);

	//计算end_seq,实际上,end_seq是数据包的结束序列号,实际上是期待TCP确认
	//包中ACK的数值,在数据传输过程中,确认包ACK的数值等于本次数据包SEQ
	//号加上本数据包的有效载荷,即skb->len - th->doff * 4,但是在处理SYN报文或者
	//FIN报文的时候,确认包的ACK等于本次处理数据包的SEQ+1,考虑到这种情况,
	//期待下一个数据包的ACK就变成了TCP_SKB_CB(skb)->seq + th->syn + th->fin +
	//skb->len - th->doff * 4

	// TCP_SKB_CB宏会返回skb->cb[0],一个类型为tcp_skb_cb的结构指针,这个结
	//构保存了TCP首部选项和其他的一些状态信息

	TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
					skb->len - th->doff * 4);
	TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
	TCP_SKB_CB(skb)->when   = 0;
	TCP_SKB_CB(skb)->flags    = iph->tos;
	TCP_SKB_CB(skb)->sacked = 0;

	//根据四元组查找相应连接的sock结构,大体有两个步骤,
	//首先用__inet_lookup_established函数查找已经处于establish状态的连接,
	//如果查找不到的话,就调用__inet_lookup_listener函数查找是否存在四元组相
	//匹配的处于listen状态的sock,这个时候实际上是被动的接收来自其他主机的连接
	//请求

	//如果查找不到匹配的sock,则直接丢弃数据包
	sk = __inet_lookup(&tcp_hashinfo, iph->saddr, th->source,
			   iph->daddr, th->dest, inet_iif(skb));
	if (!sk)
		goto no_tcp_socket;

	//检查sock是否处于半关闭状态
	process:
	if (sk->sk_state == TCP_TIME_WAIT)
		goto do_time_wait;
 
	//检查IPSEC规则
	if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb))
		goto discard_and_relse;
	nf_reset(skb);

	//检查BPF规则
	if (sk_filter(sk, skb))
		goto discard_and_relse;

	skb->dev = NULL;

	//这里主要是和release_sock函数实现互斥,release_sock中调用了
	// spin_lock_bh(&sk->sk_lock.slock);
	bh_lock_sock_nested(sk);
	ret = 0;

	//查看是否有用户态进程对该sock进行了锁定
	//如果sock_owned_by_user为真,则sock的状态不能进行更改
	if (!sock_owned_by_user(sk)) {

#ifdef CONFIG_NET_DMA
		struct tcp_sock *tp = tcp_sk(sk);
		if (!tp->ucopy.dma_chan && tp->ucopy.pinned_list)
			tp->ucopy.dma_chan = get_softnet_dma();
		if (tp->ucopy.dma_chan)
			ret = tcp_v4_do_rcv(sk, skb);
		else
#endif
		{
			//进入预备处理队列
			if (!tcp_prequeue(sk, skb))
				ret = tcp_v4_do_rcv(sk, skb);
		}
	} else
		//如果数据包被用户进程锁定,则数据包进入后备处理队列,并且该进程进入
		//套接字的后备处理等待队列sk->lock.wq
		sk_add_backlog(sk, skb);
	bh_unlock_sock(sk);

	sock_put(sk);
	return ret;

no_tcp_socket:
	if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb))
		goto discard_it;

	if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {
bad_packet:
		TCP_INC_STATS_BH(TCP_MIB_INERRS);
	} else {
		tcp_v4_send_reset(NULL, skb);
	}

discard_it:
	kfree_skb(skb);
	return 0;

discard_and_relse:
	sock_put(sk);
	goto discard_it;

do_time_wait:
	if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
		inet_twsk_put(inet_twsk(sk));
		goto discard_it;
	}

	if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) {
		TCP_INC_STATS_BH(TCP_MIB_INERRS);
		inet_twsk_put(inet_twsk(sk));
		goto discard_it;
	}
	switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
	case TCP_TW_SYN: {
		struct sock *sk2 = inet_lookup_listener(&tcp_hashinfo,
							iph->daddr, th->dest,
							inet_iif(skb));
		if (sk2) {
			inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);
			inet_twsk_put(inet_twsk(sk));
			sk = sk2;
			goto process;
		}
	}
	case TCP_TW_ACK:
		tcp_v4_timewait_ack(sk, skb);
		break;
	case TCP_TW_RST:
		goto no_tcp_socket;
	case TCP_TW_SUCCESS:;
	}
	goto discard_it;
}