您的位置:首页 > 运维架构 > Linux

看我怎样模拟3次握手和数据发送(Linux)

2018-01-10 16:22 316 查看
http://blog.chinaunix.net/uid-26379600-id-3791210.html

来看源代码:

#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的时候又如何判断呢?本人正在探索中,一旦有新进展将第一时间与大家分享。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: