看我怎样模拟3次握手和数据发送(Linux)
2018-01-10 16:22
316 查看
http://blog.chinaunix.net/uid-26379600-id-3791210.html
来看源代码:
模拟了一下TCP的三次握手,写得比较粗糙。
sk = socket (PF_PACKET, SOCK_DGRAM, htons (ETH_P_ALL));
主要是创建一个PF_PACKET的套接字,它是数据链路层的套接字,绕过(bypass)系统的协议栈,然后自己构造IP层和TCP层就可以了。
运行之前先执行一下iptables -I INPUT -j DROP把来的数据都扔掉,要不然等服务器发回SYN ACK的时候会被系统的协议栈返回个RST。
可以打开wireshark看效果。
Packet套接字用于在MAC层上收发原始数据帧,这样就允许用户在用户空间完成MAC之上各个层次的实现。给无论是进行开发还是测试的人们带来了极大的便利性。
Packet套接字的定义方式与传送层的套接字定义类似,如下:
packet_socket=socket(PF_PACKET,int socket_type,int protocol);
(这个套接字的打开需要用户有root权限)
其中socket_type有两种类型,一种为SOCK_RAW,它是包含了MAC层头部信息的原始分组,当然这种类型的套接字在发送的时候需要自己加上一个MAC头部(其类型定义在linux/if_ether.h中,ethhdr),另一种是SOCK_DGRAM类型,它是已经进行了MAC层头部处理的,即收上的帧已经去掉了头部,而发送时也无须用户添加头部字段。
Protocol是指其送交的上层的协议号,如IP为0x0800,当其为htons(ETH_P_ALL) (其宏定义为0)时表示收发所有的协议。
创建好套接字后,就可以通过与UDP一样的recvfrom与sendto函数进行数据的收发,其目的地址结构为sockaddr_ll,这与传送层的地址结构定义是不一样的,其长度为20字节(在TCP/IP的链路层地址中使用了18字节),而传送层的地址结构长度为16字节。
Sockaddr_ll结构如下:
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_protocol 是在 linux/if_ether.h 头文件中定义的按网络层排序的标准的以太桢协议类型。sll_ifindex 是接口的索引号(参见netdevice(2));0 匹配所有的接口(当然只有合法的才用于绑定)。 sll_hatype 是在 linux/if_arp.h 中定义的 ARP 硬件地址类型。 sll_pkttype 包含分组类型。有效的分组类型是:目标地址是本地主机的分组用的 PACKET_HOST,物理层广播分组用的 PACKET_BROADCAST ,发送到一个物理层多路广播地址的分组用的
PACKET_MULTICAST,在混杂(promiscuous)模式下的设备驱动器发向其他主机的分组用的 PACKET_OTHERHOST,本源于本地主机的分组被环回到分组套接口用的 PACKET_OUTGOING。这些类型只对接收到的分组有意义。sll_addr 和 sll_halen 包括物理层(例如 IEEE 802.3)地址和地址长度。精确的解释依赖于设备。(本段引于packet的用户手册)
当在多个网络接口的主机上使用这个套接字时,若要指定接收或发送的接口时可以使用bind进行绑定,这与TCP套接字的操作一样,但其内涵并不相同。绑定时将根据地址结构中的sll_protocal和sll_ifindex分别绑定收发的协议号和接口索引号,接口索引号sll_ifindex为0时表示使用有效的所有接口。接口的sll_ifindex值可以通过ioctl获得,如下面是获得名字为“eth0”的接口的索引号
strcpy(ifr.ifr_name,"eth0");
ioctl(fd_packet,SIOCGIFINDEX,&ifr);
取得的值保存在ifr结构体的ifr_ifindex中,ifr结构类型为“struct ifreq”
BTW,要获得接口的物理地址同样使用ioctl可以得到
ioctl(fd_packet,SIOCGIFHWADDR,&ifr);
以数据形式保存在ifr的ifr_hwaddr.sa_data中。
另外需要注意的是,在调用recvfrom函数时返回的地址长度信息为18字节,原因是在sockaddr_ll结构中的sll_addr[8]为8字节,MAC地址只使用了其中的前6字节。在用sendto发送时需要将目的地址结构体强制转换为struct sockaddr 类型,而且指定的长度必须为20字节,而不能是18或其它值。
我在使用中当指定了协议类型后可以准备接收该类型的数据帧,但有个问题一直困扰着我,就是无法过滤掉广播帧,必须要收到帧后判断目的地址是否为自己,然后如果用SOCK_DGRAM的时候又如何判断呢?本人正在探索中,一旦有新进展将第一时间与大家分享。
来看源代码:
#include <stdio.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <linux/if_ether.h> #include <linux/sockios.h> #include <sys/socket.h> #include <netpacket/packet.h> #include <net/ethernet.h> #include <net/if_arp.h> #include <net/if.h> #define DESTPORT 80 #define LOCALPORT 0x8888 /* Buffer Size */ #define SND_BUF_SIZE 1024*5 /* Buffer */ static int g_iSendBufSize = SND_BUF_SIZE; static int g_iRecvBufSize = SND_BUF_SIZE; static unsigned long seqno, ackno; //save sequence no and ackment no extern int errno; /* Prseuheader */ struct prseuheader { unsigned long s_addr; unsigned long d_addr; unsigned char zero; unsigned char prototp; unsigned short len; }; /* IP Head */ struct IP_Head { unsigned char length:4; unsigned char version:4; unsigned char tos; unsigned short total_length; unsigned short id; unsigned short flagoff; unsigned char ttl; unsigned char protocol; unsigned short chksum; unsigned int source; unsigned int dest; }; /* TCP Head */ struct TCP_Head { unsigned short source_port; unsigned short dest_port; unsigned int seqno; unsigned int ackno; unsigned char rev1:4; unsigned char len:4; unsigned char fin:1; unsigned char syn:1; unsigned char rst:1; unsigned char psh:1; unsigned char ack:1; unsigned char urg:1; unsigned char rev2:2; unsigned short winsize; unsigned short chksum; unsigned short urgent; }; /* Check sum */ unsigned short checksum (unsigned short *buffer, int size) { unsigned long cksum = 0; while (size > 1) { cksum += *buffer++; size -= 2; } if (size) { cksum += *(u_char *) buffer; } cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >> 16); return (unsigned short) (~cksum); } //Create SOCK_RAW int creat_raw () { int sk; sk = socket (PF_PACKET, SOCK_DGRAM, htons (ETH_P_ALL)); if (sk < 0) { strerror (errno); return -1; } return sk; } //Set sock option int set_promisc (int sk) { // struct ifreq ifr; // // strcpy (ifr.ifr_name, "eth0"); // if ((ioctl (sk, SIOCGIFFLAGS, &ifr) == -1)) // { // strerror (errno); // } // ifr.ifr_flags |= IFF_PROMISC; // if (ioctl (sk, SIOCSIFFLAGS, &ifr) == -1) // { // strerror (errno); // } return 0; } /* fill ip head */ int fill_iph (unsigned char buffer[]) { struct IP_Head *pIph; int total_len = sizeof (struct IP_Head) + sizeof (struct TCP_Head); pIph = (struct IP_Head *) buffer; pIph->length = 5; pIph->version = 4; pIph->tos = 0; pIph->total_length = htons (total_len); pIph->id = 0; pIph->flagoff = htons (0x4000); pIph->ttl = 255; pIph->protocol = 6; pIph->chksum = 0; pIph->source = inet_addr ("192.168.1.2"); //Local PC 写上自己网卡的IP pIph->dest = inet_addr ("119.75.217.56"); //remote PC 服务器的IP pIph->chksum = checksum ((unsigned short *) pIph, sizeof (struct IP_Head)); return 0; } /* fill tcp head */ int fill_tcp (unsigned char buffer[], unsigned char control) { struct TCP_Head *pTcph; pTcph = (struct TCP_Head *) (buffer + sizeof (struct IP_Head)); pTcph->source_port = htons (LOCALPORT); pTcph->dest_port = htons (DESTPORT); pTcph->seqno = htonl (seqno); pTcph->ackno = htonl (ackno); pTcph->rev1 = 0; pTcph->len = 5; pTcph->fin = control & 0x01 ? 1 : 0; pTcph->syn = control & 0x02 ? 1 : 0; pTcph->rst = control & 0x04 ? 1 : 0; pTcph->psh = control & 0x10 ? 1 : 0; pTcph->ack = control & 0x20 ? 1 : 0; pTcph->urg = control & 0x40 ? 1 : 0; pTcph->rev2 = 0; pTcph->winsize = htons (1000); pTcph->chksum = 0; pTcph->urgent = 0; return 0; } //send tcp packet int sendto_packet (int sk, unsigned char buffer[]) { int iRet; struct IP_Head *pIph; struct TCP_Head *pTcph; struct prseuheader theheader; char tcpbuff[32]; //it include prseuheader and tcp head int total_len = sizeof (struct IP_Head) + sizeof (struct TCP_Head); struct sockaddr_ll addr; pIph = (struct IP_Head *) buffer; pTcph = (struct TCP_Head *) (buffer + sizeof (struct IP_Head)); bzero (&addr, sizeof (struct sockaddr_ll)); addr.sll_family = htons (PF_PACKET); addr.sll_protocol = htons (ETH_P_IP); addr.sll_ifindex = if_nametoindex ("eth0");//换成你的网口的名字 addr.sll_addr[0] = 0x08;// addr.sll_addr[1] = 0x10;// addr.sll_addr[2] = 0x74;//换成你的网关的MAC地址就行了 addr.sll_addr[3] = 0xC9;// addr.sll_addr[4] = 0x0B;// addr.sll_addr[5] = 0x16;// bzero (tcpbuff, 32); theheader.s_addr = pIph->source; theheader.d_addr = pIph->dest; theheader.zero = 0; theheader.prototp = 6; theheader.len = htons (20); //the size of TCP head memcpy (tcpbuff, &theheader, 12); memcpy (tcpbuff + 12, pTcph, 20); pTcph->chksum = 0; pTcph->chksum = checksum ((unsigned short *) tcpbuff, 32); iRet = sendto (sk, buffer, total_len, 0, (struct sockaddr *) &addr, sizeof (struct sockaddr_ll)); if (iRet < 0) { strerror (errno); return -1; } return 0; } //receive tcp packet and display IP Head and TCP Head int recvfrom_packet (int sk, unsigned char buffer[]) { int iRet; int lenfrom = sizeof (struct sockaddr_in); struct sockaddr_in addr; struct in_addr in; struct IP_Head *pIph; struct TCP_Head *pTcph; fd_set fdR; struct timeval timeout; //Set Non-block bzero (&addr, sizeof (struct sockaddr_in)); while (1) { iRet = recvfrom (sk, buffer, g_iRecvBufSize, 0, (struct sockaddr *) &addr, &lenfrom); if (iRet < 0) { printf ("error recvfrom\n"); return -1; } else { pIph = (struct IP_Head *) buffer; pTcph = (struct TCP_Head *) (buffer + pIph->length * 4); if ((pIph->protocol!=6) || ntohs (pTcph->dest_port) != 0x8888) { continue; } //Display IP Head printf ("Parse IP......\n"); printf ("length:%x\n", pIph->length); printf ("version:%x\n", pIph->version); printf ("tos:%x\n", pIph->tos); printf ("total_length:%d\n", ntohs (pIph->total_length)); printf ("id:%d\n", ntohs (pIph->id)); printf ("flagoff:%x\n", ntohs (pIph->flagoff)); printf ("ttl:%d\n", pIph->ttl); printf ("protocol:%d\n", pIph->protocol); printf ("cksum:%x\n", ntohs (pIph->chksum)); in.s_addr = pIph->source; printf ("SIP:%s\n", inet_ntoa (in)); in.s_addr = pIph->dest; printf ("DIP:%s\n", inet_ntoa (in)); //Display TCP Head printf ("Parse TCP......\n"); printf ("source_port:%d\n", ntohs (pTcph->source_port)); printf ("dest_port:%d\n", ntohs (pTcph->dest_port)); printf ("seqno:%d\n", ntohl (pTcph->seqno)); printf ("ackno:%d\n", ntohl (pTcph->ackno)); printf ("len:%d\n", pTcph->len); printf ("fin:%d\n", pTcph->fin); printf ("syn:%d\n", pTcph->syn); printf ("rst:%d\n", pTcph->rst); printf ("psh:%d\n", pTcph->psh); printf ("ack:%d\n", pTcph->ack); printf ("urg:%d\n", pTcph->urg); printf ("winsize:%d\n", ntohs (pTcph->winsize)); printf ("urgent:%d\n", ntohs (pTcph->urgent)); printf ("\n"); seqno = ntohl (pTcph->seqno); ackno = ntohl (pTcph->ackno); return; } } } //Main int main () { int sk; unsigned char buffers[g_iSendBufSize]; //send buffer unsigned char bufferr[g_iRecvBufSize]; //receive buffer int iRet; unsigned long temp; sk = creat_raw (); if (sk < 0) { printf ("Creat socket error.\n"); return -1; } iRet = set_promisc (sk); if (iRet < 0) { printf ("Set socket promisc error.\n"); close (sk); return -1; } //the first bzero (buffers, g_iSendBufSize); bzero (bufferr, g_iRecvBufSize); fill_iph (buffers); seqno = 0; ackno = 0; fill_tcp (buffers, 0x02); iRet = sendto_packet (sk, buffers); if (iRet < 0) { printf ("Sendto_packet error.\n"); close (sk); return -1; } iRet = recvfrom_packet (sk, bufferr); if (iRet < 0) { printf ("Recvfrom_packet error.\n"); printf ("time is over or error opertion \n"); close (sk); return -1; } //the third bzero (buffers, g_iSendBufSize); bzero (bufferr, g_iRecvBufSize); fill_iph (buffers); temp = seqno; seqno = ackno; ackno = temp + 1; fill_tcp (buffers, 0x20); iRet = sendto_packet (sk, buffers); if (iRet < 0) { printf ("Sendto_packet error.\n"); close (sk); return -1; } close (sk); return 0; }
模拟了一下TCP的三次握手,写得比较粗糙。
sk = socket (PF_PACKET, SOCK_DGRAM, htons (ETH_P_ALL));
主要是创建一个PF_PACKET的套接字,它是数据链路层的套接字,绕过(bypass)系统的协议栈,然后自己构造IP层和TCP层就可以了。
运行之前先执行一下iptables -I INPUT -j DROP把来的数据都扔掉,要不然等服务器发回SYN ACK的时候会被系统的协议栈返回个RST。
可以打开wireshark看效果。
Packet套接字用于在MAC层上收发原始数据帧,这样就允许用户在用户空间完成MAC之上各个层次的实现。给无论是进行开发还是测试的人们带来了极大的便利性。
Packet套接字的定义方式与传送层的套接字定义类似,如下:
packet_socket=socket(PF_PACKET,int socket_type,int protocol);
(这个套接字的打开需要用户有root权限)
其中socket_type有两种类型,一种为SOCK_RAW,它是包含了MAC层头部信息的原始分组,当然这种类型的套接字在发送的时候需要自己加上一个MAC头部(其类型定义在linux/if_ether.h中,ethhdr),另一种是SOCK_DGRAM类型,它是已经进行了MAC层头部处理的,即收上的帧已经去掉了头部,而发送时也无须用户添加头部字段。
Protocol是指其送交的上层的协议号,如IP为0x0800,当其为htons(ETH_P_ALL) (其宏定义为0)时表示收发所有的协议。
创建好套接字后,就可以通过与UDP一样的recvfrom与sendto函数进行数据的收发,其目的地址结构为sockaddr_ll,这与传送层的地址结构定义是不一样的,其长度为20字节(在TCP/IP的链路层地址中使用了18字节),而传送层的地址结构长度为16字节。
Sockaddr_ll结构如下:
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_protocol 是在 linux/if_ether.h 头文件中定义的按网络层排序的标准的以太桢协议类型。sll_ifindex 是接口的索引号(参见netdevice(2));0 匹配所有的接口(当然只有合法的才用于绑定)。 sll_hatype 是在 linux/if_arp.h 中定义的 ARP 硬件地址类型。 sll_pkttype 包含分组类型。有效的分组类型是:目标地址是本地主机的分组用的 PACKET_HOST,物理层广播分组用的 PACKET_BROADCAST ,发送到一个物理层多路广播地址的分组用的
PACKET_MULTICAST,在混杂(promiscuous)模式下的设备驱动器发向其他主机的分组用的 PACKET_OTHERHOST,本源于本地主机的分组被环回到分组套接口用的 PACKET_OUTGOING。这些类型只对接收到的分组有意义。sll_addr 和 sll_halen 包括物理层(例如 IEEE 802.3)地址和地址长度。精确的解释依赖于设备。(本段引于packet的用户手册)
当在多个网络接口的主机上使用这个套接字时,若要指定接收或发送的接口时可以使用bind进行绑定,这与TCP套接字的操作一样,但其内涵并不相同。绑定时将根据地址结构中的sll_protocal和sll_ifindex分别绑定收发的协议号和接口索引号,接口索引号sll_ifindex为0时表示使用有效的所有接口。接口的sll_ifindex值可以通过ioctl获得,如下面是获得名字为“eth0”的接口的索引号
strcpy(ifr.ifr_name,"eth0");
ioctl(fd_packet,SIOCGIFINDEX,&ifr);
取得的值保存在ifr结构体的ifr_ifindex中,ifr结构类型为“struct ifreq”
BTW,要获得接口的物理地址同样使用ioctl可以得到
ioctl(fd_packet,SIOCGIFHWADDR,&ifr);
以数据形式保存在ifr的ifr_hwaddr.sa_data中。
另外需要注意的是,在调用recvfrom函数时返回的地址长度信息为18字节,原因是在sockaddr_ll结构中的sll_addr[8]为8字节,MAC地址只使用了其中的前6字节。在用sendto发送时需要将目的地址结构体强制转换为struct sockaddr 类型,而且指定的长度必须为20字节,而不能是18或其它值。
我在使用中当指定了协议类型后可以准备接收该类型的数据帧,但有个问题一直困扰着我,就是无法过滤掉广播帧,必须要收到帧后判断目的地址是否为自己,然后如果用SOCK_DGRAM的时候又如何判断呢?本人正在探索中,一旦有新进展将第一时间与大家分享。
相关文章推荐
- 看我怎样模拟3次握手和数据发送(windows)
- linux串口驱动分析——发送数据
- Linux下串口发送数据一段数据后发送不出去(可以正常接收)
- 如何获取摄像头的控制指令数据,进行模拟发送控制
- 关于模拟SPI发送数据中一个小细节
- C#模拟Post和Get方式发送数据 保持COOKIE
- C# POST 模拟发送提交数据
- PHP利用socket模拟post之fsockopen发送数据
- Linux 自动任务生成数据和发送邮件
- [Linux]非阻塞模式下socket发送数据
- linux下查询mysql数据保存xls并自动发送邮件
- linux内核网络,数据发送流程图
- linux下使用C语言接收和发送udp组播数据分别怎么写?
- php模拟socket一次连接,多次发送数据的实现代码
- Linux 自动任务生成数据和发送邮件
- 简要分析并搞懂9个tcp基础包------三次握手 + 发送数据并收到确认 + 四次挥手
- linux2.4 GPIO模拟实现I2C数据传输-DS1302
- 简析TCP的三次握手与四次分手(TCP协议头部的格式,数据从应用层发下来,会在每一层都会加上头部信息,进行封装,然后再发送到数据接收端)good
- linux 串口编程 用read函数读取数据被截断 怎样一次读完