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

186 阅读5分钟

blog.chinaunix.net/uid-2637960…\

来看源代码:

#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的时候又如何判断呢?本人正在探索中,一旦有新进展将第一时间与大家分享。\