一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

Linux|Centos|Ubuntu|系統進程|Fedora|注冊表|Bios|Solaris|Windows7|Windows10|Windows11|windows server|

服務器之家 - 服務器系統 - Linux - Linux編程之PING實現

Linux編程之PING實現

2021-12-24 15:58冠軍的試煉 Linux

這篇文章主要為大家詳細介紹了Linux編程之PING實現的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下

PING(Packet InterNet Groper)中文名為因特網包探索器,是用來查看網絡上另一個主機系統的網絡連接是否正常的一個工具。ping命令的工作原理是:向網絡上的另一個主機系統發送ICMP報文,如果指定系統得到了報文,它將把回復報文傳回給發送者,這有點象潛水艇聲納系統中使用的發聲裝置。所以,我們想知道我這臺主機能不能和另一臺進行通信,我們首先需要確認的是我們兩臺主機間的網絡是不是通的,也就是我說的話能不能傳到你那里,這是雙方進行通信的前提。在Linux下使用指令ping的方法和現象如下:

Linux編程之PING實現

PING的實現看起來并不復雜,我想自己寫代碼實現這個功能,需要些什么知識儲備?我簡單羅列了一下:

·ICMP協議的理解

·RAW套接字

·網絡封包和解包技能

搭建這么一個ping程序的步驟如下:

1、ICMP包的封裝和解封

2、創建一個線程用于ICMP包的發送

3、創建一個線程用于ICMP包的接收

4、原始套接字編程

PING的流程如下:

Linux編程之PING實現

一、ICMP包的封裝和解封

(1) ICMP協議理解要進行PING的開發,我們首先需要知道PING的實現是基于ICMP協議來開發的。要進行ICMP包的封裝和解封,我們首先需要理解ICMP協議。ICMP位于網絡層,允許主機或者路由器報告差錯情況和提供有關異常情況的報告。ICMP報文是封裝在IP數據報中,作為其中的數據部分。ICMP報文作為IP層數據報的數據,加上數據報頭,組成IP數據報發送出去。ICMP報文格式如下:

Linux編程之PING實現

ICMP報文的種類有兩種,即ICMP差錯報告報文和ICMP詢問報文。PING程序使用的ICMP報文種類為ICMP詢問報文。注意一下上面說到的ICMP報文格式中的“類型”字段,我們在組包的時候可以向該字段填寫不同的值來標定該ICMP報文的類型。下面列出的是幾種常用的ICMP報文類型。

Linux編程之PING實現

我們的PING程序需要用到的ICMP的類型是回送請求(8)。
因為ICMP報文的具體格式會因為ICMP報文的類型而各不相同,我們ping包的格式是這樣的:

Linux編程之PING實現

(2) ICMP包的組裝

對照上面的ping包格式,我們封裝ping包的代碼可以這么寫:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void icmp_pack(struct icmp* icmphdr, int seq, int length)
{
 int i = 0;
 
 icmphdr->icmp_type = ICMP_ECHO; //類型填回送請求
 icmphdr->icmp_code = 0;
 icmphdr->icmp_cksum = 0; //注意,這里先填寫0,很重要!
 icmphdr->icmp_seq = seq; //這里的序列號我們填1,2,3,4....
 icmphdr->icmp_id = pid & 0xffff; //我們使用pid作為icmp_id,icmp_id只是2字節,而pid有4字節
 for(i=0;i<length;i++)
 {
  icmphdr->icmp_data[i] = i; //填充數據段,使ICMP報文大于64B
 }
 
 icmphdr->icmp_cksum = cal_chksum((unsigned short*)icmphdr, length); //校驗和計算
}

這里再三提醒一下,icmp_cksum 必須先填寫為0再執行校驗和算法計算,否則ping時對方主機會因為校驗和計算錯誤而丟棄請求包,導致ping的失敗。我一個同事曾經就因為這么一個錯誤而排查許久,血的教訓請銘記。

這里簡單介紹一下checksum(校驗和)。

計算機網絡通信時,為了檢驗在數據傳輸過程中數據是否發生了錯誤,通常在傳輸數據的時候連同校驗和一塊傳輸,當接收端接受數據時候會從新計算校驗和,如果與原校驗和不同就視為出錯,丟棄該數據包,并返回icmp報文。

算法基本思路:

IP/ICMP/IGMP/TCP/UDP等協議的校驗和算法都是相同的,采用的都是將數據流視為16位整數流進行重復疊加計算。為了計算檢驗和,首先把檢驗和字段置為0。然后,對有效數據范圍內中每個16位進行二進制反碼求和,結果存在檢驗和字段中,如果數據長度為奇數則補一字節0。當收到數據后,同樣對有效數據范圍中每個16位數進行二進制反碼的求和。由于接收方在計算過程中包含了發送方存在首部中的檢驗和,因此,如果首部在傳輸過程中沒有發生任何差錯,那么接收方計算的結果應該為全0或全1(具體看實現了,本質一樣) 。如果結果不是全0或全1,那么表示數據錯誤。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*校驗和算法*/
unsigned short cal_chksum(unsigned short *addr,int len)
int nleft=len;
  int sum=0;
  unsigned short *w=addr;
  unsigned short answer=0;
 
  /*把ICMP報頭二進制數據以2字節為單位累加起來*/
  while(nleft>1)
  
   sum+=*w++;
   nleft-=2;
  }
  /*若ICMP報頭為奇數個字節,會剩下最后一字節。把最后一個字節視為一個2字節數據的高字節,這個2字節數據的低字節為0,繼續累加*/
  if( nleft==1)
  
   *(unsigned char *)(&answer)=*(unsigned char *)w;
   sum+=answer;
  }
  sum=(sum>>16)+(sum&0xffff);
  sum+=(sum>>16);
  answer=~sum;
  return answer;
}

(3) ICMP包的解包

知道怎么封裝包,那解包就也不難了,注意的是,收到一個ICMP包,我們不要就認為這個包就是我們發出去的ICMP回送回答包,我們需要加一層代碼來判斷該ICMP報文的id和seq字段是否符合我們發送的ICMP報文的設置,來驗證ICMP回復包的正確性。

?
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
int icmp_unpack(char* buf, int len)
{
 int iphdr_len;
 struct timeval begin_time, recv_time, offset_time;
 int rtt; //round trip time
 
 struct ip* ip_hdr = (struct ip *)buf;
 iphdr_len = ip_hdr->ip_hl*4;
 struct icmp* icmp = (struct icmp*)(buf+iphdr_len); //使指針跳過IP頭指向ICMP頭
 len-=iphdr_len; //icmp包長度
 if(len < 8) //判斷長度是否為ICMP包長度
 {
  fprintf(stderr, "Invalid icmp packet.Its length is less than 8\n");
  return -1;
 }
 
 //判斷該包是ICMP回送回答包且該包是我們發出去的
 if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid))
 {
  if((icmp->icmp_seq < 0) || (icmp->icmp_seq > PACKET_SEND_MAX_NUM))
  {
   fprintf(stderr, "icmp packet seq is out of range!\n");
   return -1;
  }
 
  ping_packet[icmp->icmp_seq].flag = 0;
  begin_time = ping_packet[icmp->icmp_seq].begin_time; //去除該包的發出時間
  gettimeofday(&recv_time, NULL);
 
  offset_time = cal_time_offset(begin_time, recv_time);
  rtt = offset_time.tv_sec*1000 + offset_time.tv_usec/1000; //毫秒為單位
 
  printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n",
   len, inet_ntoa(ip_hdr->ip_src), icmp->icmp_seq, ip_hdr->ip_ttl, rtt); 
 
 }
 else
 {
  fprintf(stderr, "Invalid ICMP packet! Its id is not matched!\n");
  return -1;
 }
 return 0;
}

二、發包線程的搭建

根據PING程序的框架,我們需要建立一個線程用于ping包的發送,我的想法是這樣的:使用sendto進行發包,發包速率我們維持在1秒1發,我們需要用一個全局變量記錄第一個ping包發出的時間,除此之外,我們還需要一個全局變量來記錄我們發出的ping包到底有幾個,這兩個變量用于后來收到ping包回復后的數據計算。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void ping_send()
{
 char send_buf[128];
 memset(send_buf, 0, sizeof(send_buf));
 gettimeofday(&start_time, NULL); //記錄第一個ping包發出的時間
 while(alive)
 {
  int size = 0;
  gettimeofday(&(ping_packet[send_count].begin_time), NULL);
  ping_packet[send_count].flag = 1; //將該標記為設置為該包已發送
 
  icmp_pack((struct icmp*)send_buf, send_count, 64); //封裝icmp包
  size = sendto(rawsock, send_buf, 64, 0, (struct sockaddr*)&dest, sizeof(dest));
  send_count++; //記錄發出ping包的數量
  if(size < 0)
  {
   fprintf(stderr, "send icmp packet fail!\n");
   continue;
  }
 
  sleep(1);
 }
}

三、收包線程的搭建
我們同樣建立一個接收包的線程,這里我們采用select函數進行收包,并為select函數設置超時時間為200us,若發生超時,則進行下一個循環。同樣地,我們也需要一個全局變量來記錄成功接收到的ping回復包的數量。

?
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
void ping_recv()
{
 struct timeval tv;
 tv.tv_usec = 200; //設置select函數的超時時間為200us
 tv.tv_sec = 0;
 fd_set read_fd;
 char recv_buf[512];
 memset(recv_buf, 0 ,sizeof(recv_buf));
 while(alive)
 {
  int ret = 0;
  FD_ZERO(&read_fd);
  FD_SET(rawsock, &read_fd);
  ret = select(rawsock+1, &read_fd, NULL, NULL, &tv);
  switch(ret)
  {
   case -1:
    fprintf(stderr,"fail to select!\n");
    break;
   case 0:
    break;
   default:
    {
     int size = recv(rawsock, recv_buf, sizeof(recv_buf), 0);
     if(size < 0)
     {
      fprintf(stderr,"recv data fail!\n");
      continue;
     }
 
     ret = icmp_unpack(recv_buf, size); //對接收的包進行解封
     if(ret == -1) //不是屬于自己的icmp包,丟棄不處理
     {
      continue;
     }
     recv_count++; //接收包計數
    }
    break;
  }
 
 }
}<strong>
</strong>

 

四、中斷處理

我們規定了一次ping發送的包的最大值為64個,若超出該數值就停止發送。作為PING的使用者,我們一般只會發送若干個包,若有這幾個包順利返回,我們就crtl+c中斷ping。這里的代碼主要是為中斷信號寫一個中斷處理函數,將alive這個全局變量設置為0,進而使發送ping包的循環停止而結束程序。

?
1
2
3
4
5
6
7
8
void icmp_sigint(int signo)
{
 alive = 0;
 gettimeofday(&end_time, NULL);
 time_interval = cal_time_offset(start_time, end_time);
}
 
signal(SIGINT, icmp_sigint);

五、總體實現

各模塊介紹完了,現在貼出完整代碼。

?
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
#include <stdio.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/time.h>
#include <string.h>
#include <netdb.h>
#include <pthread.h>
 
 
#define PACKET_SEND_MAX_NUM 64
 
typedef struct ping_packet_status
{
 struct timeval begin_time;
 struct timeval end_time;
 int flag; //發送標志,1為已發送
 int seq;  //包的序列號
}ping_packet_status;
 
 
 
ping_packet_status ping_packet[PACKET_SEND_MAX_NUM];
 
int alive;
int rawsock;
int send_count;
int recv_count;
pid_t pid;
struct sockaddr_in dest;
struct timeval start_time;
struct timeval end_time;
struct timeval time_interval;
 
/*校驗和算法*/
unsigned short cal_chksum(unsigned short *addr,int len)
int nleft=len;
  int sum=0;
  unsigned short *w=addr;
  unsigned short answer=0;
 
  /*把ICMP報頭二進制數據以2字節為單位累加起來*/
  while(nleft>1)
  
   sum+=*w++;
   nleft-=2;
  }
  /*若ICMP報頭為奇數個字節,會剩下最后一字節。把最后一個字節視為一個2字節數據的高字節,這個2字節數據的低字節為0,繼續累加*/
  if( nleft==1)
  
   *(unsigned char *)(&answer)=*(unsigned char *)w;
   sum+=answer;
  }
  sum=(sum>>16)+(sum&0xffff);
  sum+=(sum>>16);
  answer=~sum;
  return answer;
}
 
struct timeval cal_time_offset(struct timeval begin, struct timeval end)
{
 struct timeval ans;
 ans.tv_sec = end.tv_sec - begin.tv_sec;
 ans.tv_usec = end.tv_usec - begin.tv_usec;
 if(ans.tv_usec < 0) //如果接收時間的usec小于發送時間的usec,則向sec域借位
 {
  ans.tv_sec--;
  ans.tv_usec+=1000000;
 }
 return ans;
}
 
void icmp_pack(struct icmp* icmphdr, int seq, int length)
{
 int i = 0;
 
 icmphdr->icmp_type = ICMP_ECHO;
 icmphdr->icmp_code = 0;
 icmphdr->icmp_cksum = 0;
 icmphdr->icmp_seq = seq;
 icmphdr->icmp_id = pid & 0xffff;
 for(i=0;i<length;i++)
 {
  icmphdr->icmp_data[i] = i;
 }
 
 icmphdr->icmp_cksum = cal_chksum((unsigned short*)icmphdr, length);
}
 
int icmp_unpack(char* buf, int len)
{
 int iphdr_len;
 struct timeval begin_time, recv_time, offset_time;
 int rtt; //round trip time
 
 struct ip* ip_hdr = (struct ip *)buf;
 iphdr_len = ip_hdr->ip_hl*4;
 struct icmp* icmp = (struct icmp*)(buf+iphdr_len);
 len-=iphdr_len; //icmp包長度
 if(len < 8) //判斷長度是否為ICMP包長度
 {
  fprintf(stderr, "Invalid icmp packet.Its length is less than 8\n");
  return -1;
 }
 
 //判斷該包是ICMP回送回答包且該包是我們發出去的
 if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == pid))
 {
  if((icmp->icmp_seq < 0) || (icmp->icmp_seq > PACKET_SEND_MAX_NUM))
  {
   fprintf(stderr, "icmp packet seq is out of range!\n");
   return -1;
  }
 
  ping_packet[icmp->icmp_seq].flag = 0;
  begin_time = ping_packet[icmp->icmp_seq].begin_time;
  gettimeofday(&recv_time, NULL);
 
  offset_time = cal_time_offset(begin_time, recv_time);
  rtt = offset_time.tv_sec*1000 + offset_time.tv_usec/1000; //毫秒為單位
 
  printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n",
   len, inet_ntoa(ip_hdr->ip_src), icmp->icmp_seq, ip_hdr->ip_ttl, rtt); 
 
 }
 else
 {
  fprintf(stderr, "Invalid ICMP packet! Its id is not matched!\n");
  return -1;
 }
 return 0;
}
 
void ping_send()
{
 char send_buf[128];
 memset(send_buf, 0, sizeof(send_buf));
 gettimeofday(&start_time, NULL); //記錄第一個ping包發出的時間
 while(alive)
 {
  int size = 0;
  gettimeofday(&(ping_packet[send_count].begin_time), NULL);
  ping_packet[send_count].flag = 1; //將該標記為設置為該包已發送
 
  icmp_pack((struct icmp*)send_buf, send_count, 64); //封裝icmp包
  size = sendto(rawsock, send_buf, 64, 0, (struct sockaddr*)&dest, sizeof(dest));
  send_count++; //記錄發出ping包的數量
  if(size < 0)
  {
   fprintf(stderr, "send icmp packet fail!\n");
   continue;
  }
 
  sleep(1);
 }
}
 
void ping_recv()
{
 struct timeval tv;
 tv.tv_usec = 200; //設置select函數的超時時間為200us
 tv.tv_sec = 0;
 fd_set read_fd;
 char recv_buf[512];
 memset(recv_buf, 0 ,sizeof(recv_buf));
 while(alive)
 {
  int ret = 0;
  FD_ZERO(&read_fd);
  FD_SET(rawsock, &read_fd);
  ret = select(rawsock+1, &read_fd, NULL, NULL, &tv);
  switch(ret)
  {
   case -1:
    fprintf(stderr,"fail to select!\n");
    break;
   case 0:
    break;
   default:
    {
     int size = recv(rawsock, recv_buf, sizeof(recv_buf), 0);
     if(size < 0)
     {
      fprintf(stderr,"recv data fail!\n");
      continue;
     }
 
     ret = icmp_unpack(recv_buf, size); //對接收的包進行解封
     if(ret == -1) //不是屬于自己的icmp包,丟棄不處理
     {
      continue;
     }
     recv_count++; //接收包計數
    }
    break;
  }
 
 }
}
 
void icmp_sigint(int signo)
{
 alive = 0;
 gettimeofday(&end_time, NULL);
 time_interval = cal_time_offset(start_time, end_time);
}
 
void ping_stats_show()
{
 long time = time_interval.tv_sec*1000+time_interval.tv_usec/1000;
 /*注意除數不能為零,這里send_count有可能為零,所以運行時提示錯誤*/
 printf("%d packets transmitted, %d recieved, %d%c packet loss, time %ldms\n",
  send_count, recv_count, (send_count-recv_count)*100/send_count, '%', time);
}
 
 
int main(int argc, char* argv[])
{
 int size = 128*1024;//128k
 struct protoent* protocol = NULL;
 char dest_addr_str[80];
 memset(dest_addr_str, 0, 80);
 unsigned int inaddr = 1;
 struct hostent* host = NULL;
 
 pthread_t send_id,recv_id;
 
 if(argc < 2)
 {
  printf("Invalid IP ADDRESS!\n");
  return -1;
 }
 
 protocol = getprotobyname("icmp"); //獲取協議類型ICMP
 if(protocol == NULL)
 {
  printf("Fail to getprotobyname!\n");
  return -1;
 }
 
 memcpy(dest_addr_str, argv[1], strlen(argv[1])+1);
 
 rawsock = socket(AF_INET,SOCK_RAW,protocol->p_proto);
 if(rawsock < 0)
 {
  printf("Fail to create socket!\n");
  return -1;
 }
 
 pid = getpid();
 
 setsockopt(rawsock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)); //增大接收緩沖區至128K
 
 bzero(&dest,sizeof(dest));
 
 dest.sin_family = AF_INET;
 
 inaddr = inet_addr(argv[1]);
 if(inaddr == INADDR_NONE) //判斷用戶輸入的是否為IP地址還是域名
 {
  //輸入的是域名地址
  host = gethostbyname(argv[1]);
  if(host == NULL)
  {
   printf("Fail to gethostbyname!\n");
   return -1;
  }
 
  memcpy((char*)&dest.sin_addr, host->h_addr, host->h_length);
 }
 else
 {
  memcpy((char*)&dest.sin_addr, &inaddr, sizeof(inaddr));//輸入的是IP地址
 }
 inaddr = dest.sin_addr.s_addr;
 printf("PING %s, (%d.%d.%d.%d) 56(84) bytes of data.\n",dest_addr_str,
  (inaddr&0x000000ff), (inaddr&0x0000ff00)>>8,
  (inaddr&0x00ff0000)>>16, (inaddr&0xff000000)>>24);
 
 alive = 1; //控制ping的發送和接收
 
 signal(SIGINT, icmp_sigint);
 
 if(pthread_create(&send_id, NULL, (void*)ping_send, NULL))
 {
  printf("Fail to create ping send thread!\n");
  return -1;
 }
 
 if(pthread_create(&recv_id, NULL, (void*)ping_recv, NULL))
 {
  printf("Fail to create ping recv thread!\n");
  return -1;
 }
 
 pthread_join(send_id, NULL);//等待send ping線程結束后進程再結束
 pthread_join(recv_id, NULL);//等待recv ping線程結束后進程再結束
 
 ping_stats_show();
 
 close(rawsock);
 return 0;
 
}

編譯以及實驗現象如下:
我的實驗環境是兩臺服務器,發起ping的主機是172.0.5.183,被ping的主機是172.0.5.182,以下是我的兩次實驗現象(ping IP和ping 域名)。

特別注意:

只有root用戶才能利用socket()函數生成原始套接字,要讓Linux的一般用戶能執行以上程序,需進行如下的特別操作:用root登陸,編譯以上程序gcc -lpthread -o ping ping.c

Linux編程之PING實現

實驗現象可以看出,PING是成功的,表明兩主機間的網絡是通的,發出的所有ping包都收到了回復。

下面是Linux系統自帶的PING程序,我們可以對比一下我們設計的PING程序跟系統自帶的PING程序有何不同。

Linux編程之PING實現

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 韩国久久精品 | 973影院 | 四虎在线精品观看免费 | 羞羞麻豆国产精品1区2区3区 | 欧美综合一区二区三区 | 男女男精品网站免费观看 | 亚洲欧美专区精品久久 | 欧美乱妇高清无乱码视频在线 | 成人午夜爽爽爽免费视频 | 青青在线观看 | 欧美午夜寂寞影院安卓列表 | 万域之王动漫在线观看全集免费播放 | 激情视频亚洲 | a人片| 国产综合欧美日韩视频一区 | 久久人妻少妇嫩草AV无码 | 免费一级毛片在级播放 | 欧美一区二区日韩一区二区 | 国产农村一一级特黄毛片 | 青青青手机视频在线观看 | 久久婷婷五月综合色丁香 | 黑人chinese女人 | 国产女同精品 | 久青草国产在线观看视频 | 国产精品久久久久久久久久久久久久 | 精品一区二区三区五区六区七区 | 国内9lporm自拍视频区 | 欧美艳星julnaann| 亚洲sss综合天堂久久久 | 二区三区不卡不卡视频 | 精品久久免费观看 | 古代翁熄系小说辣文 | 性bbwbbwbbwbbw撒尿 | 日本美女动态图片 | 成人免费在线视频 | 欧美激烈精交gif动态图18p | 免费在线视频网站 | 亚洲国产在线观看免费视频 | 动漫美女人物被黄漫小说 | 九九久久国产精品大片 | 久久亚洲精品专区蓝色区 |