原始 socket 编程

原始套接字简介

普通 socket 的权力和原始 socket 权力对比。

1.原始 socket 可以和内核一样直接对所有层进行操作(除了物理层)。可以更改 mac 更改 ip 更改端口。so dos 攻击就可以通过原始 socket 编程来伪造 ip 进行。 2.也可以访问经过网卡的所有数据.普通的 socket 只能访问发送给自己端口的数据。

api 介绍

int socket(int protofamily, int type, int protocol);//返回sockfd

protofamily:

这个参数指定一个协议簇,也往往被称为协议域。系统存在许多可以的协议簇,常见有AF_INET──指定为IPv4协议,AF_INET6──指定为IPv6,AF_LOCAL──指定为UNIX 协议域等等。它值都是系统预先定义的宏,系统支持哪些协议我们才可以使用,否则会调用失败。协议簇是网络层的协议。一种是处理IP层即其上的数据,通过指定socket第一个参数为AF_INET来创建这种套接字。有两种原始套接字。另一种是处理数据链路层即其上的数据,通过指定socket第一个参数为AF_PACKET来创建这种套接字。 PF_PACKET支持SOCK_DGRAM和SOCK_RAW两种socket类型。

type:

这个参数指定一个套接口的类型,套接口可能的类型有:SOCK_STREAM、SOCK_DGRAM、SOCK_SEQPACKET、SOCK_RAW等等,它们分别表明字节流、数据报、有序分组、原始套接口。这实际上是指定内核为我们提供的服务抽象,比如我们要一个字节流。需要注意的,并不是每一种协议簇都支持这里的所有的类型,所以类型与协议簇要匹配。

protocol:

指定相应的传输协议,也就是诸如TCP或UDP协议等等,系统针对每一个协议簇与类型提供了一个默认的协议,我们通过把protocol设置为0来使用这个默认的值。注意这里的协议与上面的协议簇是两个不同的概念,前者是指网络层的协议,由于它对于到传输层会出现许多协议,比如IPv4可以用来实现TCP或UDP等等传输层协议,所以称为协议簇。相应的传输层的协议就简单地称为协议。常见的协议有TCP、UDP、SCTP,要指定它们分别使用宏 IPPROTO_TCP、IPPROTO_UPD、IPPROTO_SCTP来指定。 到linux/in.h看可以使用哪些传输层的协议

例子:

socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)发送接收ip数据包 能:该套接字可以接收协议类型为(tcp udp icmp等)发往本机的ip数据包 不能:收到非发往本地ip的数据包(ip软过滤会丢弃这些不是发往本机ip的数据包) 不能:收到从本机发送出去的数据包发送的话需要自己组织tcp udp icmp等头部.可以setsockopt来自己包装ip头部这种套接字用来写个ping程序比较适合

socket(PF_PACKET, SOCK_RAW|SOCK_DGRAM, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)) 发送接收以太网数据帧这种套接字比较强大,可以监听网卡上的所有数据帧 能: 接收发往本地mac的数据帧 能: 接收从本机发送出去的数据帧(第3个参数需要设置为ETH_P_ALL) 能: 接收非发往本地mac的数据帧(网卡需要设置为promisc混杂模式) 协议类型一共有四个 ETH_P_IP 0x800 只接收发往本机mac的ip类型的数据帧 ETH_P_ARP 0x806 只接受发往本机mac的arp类型的数据帧 ETH_P_RARP 0x8035 只接受发往本机mac的rarp类型的数据帧 ETH_P_ALL 0x3 接收发往本机mac的所有类型ip arp rarp的数据帧, 接收从本机发出的所有类型的数据帧.(混杂模式打开的情况下,会接收到非发往本地mac的数据帧)

每层常用协议

链路层

报文格式

链路层有很多种协议,这个只是拿出一个典型的报文格式。

数据链路层是OSI参考模型中的第二层,介乎于物理层和网络层之间。数据链路层在物理层提供的服务的基础上向网络层提供服务,其最基本的服务是将源自网络层来的数据可靠地传输到相邻节点的目标机网络层。

数据链路层的主要协议有:

1.Point-to-Point Protocal; PPP点到点 2.Ethernet; 以太网 3.High-Level Data Link Control Protocal HDLC高级链路控制协议 4.Frame Relay; 帧中继 5.Asynchronous Transfer Mode;ATM

网络层

报文格式:

1.Version - 4位字段,指出当前使用的 IP 版本。

2.IP Header Length (IHL) ─ 指数据报协议头长度,表示协议头具有32位字长的数量。指向数据起点。正确协议头最小值为5。(实际长度为 5*4。对于 ip->ihl = sizeof( struct iphdr ) >> 2 是因为这里是以4字节为单位的。右移2位相当于除4)

3.Type-of-Service ─ 指出上层协议对处理当前数据报所期望的服务质量,并对数据报按照重要性级别进行分配。这些8位字段用于分配优先级、延迟、吞吐量以及可靠性。(即TOS)

4.Total Length ─ 指定整个 IP 数据包的字节长度,包括数据和协议头(传输层和ip层数据总和)。其最大值为65,535字节。典型的主机可以接收576字节的数据报。

5.Identification ─ 每一个IP封包都有一个16位的唯一识别码。当程序产生的数据要通过网络传送时都会被拆散成封包形式发送,当封包要进行重组的时候这个ID就是依据了。占16位。标识字段唯一地标识主机发送的每一份数据报。通常每发送一份消息它的值就会加1。RFC791认为标识字段应该由让IP发送数据报的上层来选择。假 设有两个连续的IP数据报,其中一个是由TCP生成的,而另一个是由UDP生成的,那么它们可能具有相同的标识字段。尽管这也可以照常工作(由重组算法来 处理),但是在大多数从伯克利派生出来的系统中,每发送一个IP数据报,IP层都要把一个内核变量的值加1,不管交给IP的数据来自哪一层。内核变量的初 始值根据系统引导时的时间来设置。

6.Flags ─ 由3位字段构成,其中最低位(MF)控制分片,存在下一个分片置为1,否则置0代表结束分片。中间位(DF)指出数据包是否可进行分片。第三位即最高位保留不使用,但是必须为0。

7.Fragment Offset IP协议头格式规定当封包被分段之后,由于网路情况或其它因素影响其抵达顺序不会和当初切割顺序一至,所以当封包进行分段的时候会为各片段做好定位记录,以便在重组的时候就能够对号入座。值为多少个字节,如果封包并没有被分段,则FO值为“0”。 占13位。

8.Time-to-Live生存时间字段设置了数据报可以经过的最多路由器数,表示数据包在网络上生存多久。TTL的初始值由源主机设置(通常为32或64),一旦经过一个处理它的 路由器,它的值就减去1。当该字段的值为0时,数据报就被丢弃,并发送ICMP消息通知源主机。这样当封包在传递过程中由於某些原因而未能抵达目的地的时 候就可以避免其一直充斥在网路上面。占8位。

9.Protocol ─ 指出在 IP 处理过程完成之后,有哪种上层协议接收导入数据包。

 10.Header Checksum ─ 帮助确保 IP 协议头的完整性。由于某些协议头字段的改变,如生存期(Time to Live),这就需要对每个点重新计算和检验。Internet 协议头需要进行处理。 11.Source Address ─ 源主机IP地址。 12.Destination Address ─ 目标主机IP地址。 13.Options ─ 允许 IP 支持各种选项,如安全性。这是一个可变长的字段。因为IP包头长度(Header Length)部分的单位为32bit,所以IP包头的长度必须为32bit的整数倍。因此,在可选项后面,IP协议会填充若干个0,以达到32bit的整数倍

传输层

传输层我们以 udp 为例子。tcp 还要维持链接,太烦了。dos 攻击可不需要握手建立整个链接的。

UDP 简介

UDP 报文UDP协议在IP协议上增加了复用、分用和差错检测功能。UDP的特点: 1.是无连接的,不需要链接和释放链接 2.是面向报文的,也就是说UDP协议将应用层传输下来的数据封装在一个UDP包中,不进行拆分或合并。 3.没有重传机制,是尽最大努力交付的。也就是说UDP协议无法保证数据能够准确的交付到目的主机。也不需要对接收到的UDP报文进行确认。 4.没有拥塞控制。因此UDP协议的发送速率不送网络的拥塞度影响。 5.UDP支持一对一、一对多、多对一和多对多的交互通信。

UDP报文格式

UDP协议分为首部字段和数据字段,其中首部字段只占用8个字节,分别是个占用两个字节的源端口、目的端口、长度和检验和。 1.源端口:2字节 = 16bit =0 ~ 65535 2.目的端口:2字节 3.长度:2字节 用户数据包的长度+ 8 字节固定 udp 报头。 4.检验和:2字节在进行检验和计算时,会添加一个伪首部一起进行运算。

 伪首部(占用12个字节)为 4个字节的源IP地址、4个字节的目的IP地址、1个字节的0、一个字节的数字17(协议类型 udp就是数字 17)、以及占用2个字节UDP长度。这个伪首部不是报文的真正首部,只是引入为了计算校验和。相对于IP协议的只计算首部,UDP检验和会把首部和数据一起进行校验。接收端进行的校验和与UDP报文中的校验和相与,如果无差错应该全为1。如果有误,则将报文丢弃或者发给应用层、并附上差错警告。

校验算法: 1.把校验和字段置为0 2.对IP头部中的每16bit进行二进制求和 3.如果和的高16bit不为0,则将和的高16bit和低16bit反复相加,直到和的高16bit为0,从而获得一个16bit的值 4.将该16bit的值取反,存入校验和字段。 s当接收IP包时,需要对报头进行确认,检查IP头是否有误,算法同上2、3步,然后判断取反的结果是否为0,是则正确,否则有错。

TCP 简介

1.是有连接的,面向流的。 2.是面向流的。TCP把数据流分区成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元([1] MTU)的限制) 3.有重传,去重机制。TCP协议中采用自适应的超时及重传策略。TCP的接收端必须丢弃重复的数据。 4.TCP还能提供流量控制。

TCP 报文格式

1.源端口号(16位),标识主机上发起传送的应用程序

2.目的端口(16位)标识主机上传送要到达的应用程序

3.源端和目的端的端口号,用于寻找发端和收 端应用进程。这两个值加上IP首部中的源端IP地址和目的端IP地址唯一确定一个TCP连接。

4.顺序号字段:占32比特。用来标识从TCP源端向TCP目标端发送的数据字节流,它表示在这个报文段中的第一个数据字节。

5.确认号字段:占32比特。只有ACK标志为1时,确认号字段才有效。它包含目标端所期望收到源端的下一个数据字节。

6.头部长度字段:占4比特。给出头部占32比特的数目。没有任何选项字段的TCP头部长度为20字节;最多可以有60字节的TCP头部

7.预留:由跟在数据偏移字段后的6位构成,预留位通常为0.

8.标志位字段(U、A、P、R、S、F):占6比特。各比特的含义如下:     ◆URG:紧急指针(urgent pointer)有效。     ◆ACK:确认序号有效。     ◆PSH:接收方应该尽快将这个报文段交给应用层。     ◆RST:重建连接。     ◆SYN:发起一个连接。     ◆FIN:释放一个连接。 9.窗口大小字段:占16比特。此字段用来进行流量控制。单位为字节数,这个值是本机期望一次接收的字节数。

10.TCP校验和字段:占16比特。对整个TCP报文段,即TCP头部和TCP数据进行校验和计算,并由目标端进行验证

11.紧急指针字段:占16比特。它是一个偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号

12.选项字段:占32比特。可能包括”窗口扩大因子”、”时间戳”等选项。

应用例子:抓取所有IP包

typedef struct _iphdr //定义IP首部 
{ 
    unsigned char h_verlen; //4位首部长度+4位IP版本号 
    unsigned char tos; //8位服务类型TOS 
    unsigned short total_len; //16位总长度(字节) 
    unsigned short ident; //16位标识 
    unsigned short frag_and_flags; //3位标志位 
    unsigned char ttl; //8位生存时间 TTL 
    unsigned char proto; //8位协议 (TCP, UDP 或其他) 
    unsigned short checksum; //16位IP首部校验和 
    unsigned int sourceIP; //32位源IP地址 
    unsigned int destIP; //32位目的IP地址 
}IP_HEADER; 

typedef struct _udphdr //定义UDP首部
{
    unsigned short uh_sport;    //16位源端口
    unsigned short uh_dport;    //16位目的端口
    unsigned int uh_len;//16位UDP包长度
    unsigned int uh_sum;//16位校验和
}UDP_HEADER;

typedef struct _tcphdr //定义TCP首部 
{ 
    unsigned short th_sport; //16位源端口 
    unsigned short th_dport; //16位目的端口 
    unsigned int th_seq; //32位序列号 
    unsigned int th_ack; //32位确认号 
    unsigned char th_lenres;//4位首部长度/6位保留字 
    unsigned char th_flag; //6位标志位
    unsigned short th_win; //16位窗口大小
    unsigned short th_sum; //16位校验和
    unsigned short th_urp; //16位紧急数据偏移量
}TCP_HEADER; 

typedef struct _icmphdr {  
    unsigned char  icmp_type;  
    unsigned char icmp_code; /* type sub code */  
    unsigned short icmp_cksum;  
    unsigned short icmp_id;  
    unsigned short icmp_seq;  
    /* This is not the std header, but we reserve space for time */  
    unsigned short icmp_timestamp;  
}ICMP_HEADER;

void analyseIP(IP_HEADER *ip);
void analyseTCP(TCP_HEADER *tcp);
void analyseUDP(UDP_HEADER *udp);
void analyseICMP(ICMP_HEADER *icmp);


int main(void)
{
    int sockfd;
     IP_HEADER *ip;
    char buf[10240];
    ssize_t n;
    /* capture ip datagram without ethernet header */
    if ((sockfd = socket(PF_PACKET,  SOCK_DGRAM, htons(ETH_P_IP)))== -1)
    {    
        printf("socket error!\n");
        return 1;
    }
    while (1)
    {
        n = recv(sockfd, buf, sizeof(buf), 0);
        if (n == -1)
        {
            printf("recv error!\n");
            break;
        }
        else if (n==0)
            continue;
        //接收数据不包括数据链路帧头
        ip = ( IP_HEADER *)(buf);
        analyseIP(ip);
        size_t iplen =  (ip->h_verlen&0x0f)*4;
        TCP_HEADER *tcp = (TCP_HEADER *)(buf +iplen);
        if (ip->proto == IPPROTO_TCP)
        {
            TCP_HEADER *tcp = (TCP_HEADER *)(buf +iplen);
            analyseTCP(tcp);
        }
        else if (ip->proto == IPPROTO_UDP)
        {
            UDP_HEADER *udp = (UDP_HEADER *)(buf + iplen);
            analyseUDP(udp);
        }
        else if (ip->proto == IPPROTO_ICMP)
        {
            ICMP_HEADER *icmp = (ICMP_HEADER *)(buf + iplen);
            analyseICMP(icmp);
        }
        else if (ip->proto == IPPROTO_IGMP)
        {
            printf("IGMP----\n");
        }
        else
        {
            printf("other protocol!\n");
        }        
        printf("\n\n");
    }
    close(sockfd);
    return 0;
}

void analyseIP(IP_HEADER *ip)
{
    unsigned char* p = (unsigned char*)&ip->sourceIP;
    printf("Source IP   : %u.%u.%u.%u\n",p[0],p[1],p[2],p[3]);
    p = (unsigned char*)&ip->destIP;
    printf("Destination IP  : %u.%u.%u.%u\n",p[0],p[1],p[2],p[3]);

}

void analyseTCP(TCP_HEADER *tcp)
{
    printf("TCP -----\n");
    printf("Source port: %u\n", ntohs(tcp->th_sport));
    printf("Dest port: %u\n", ntohs(tcp->th_dport));
}

void analyseUDP(UDP_HEADER *udp)
{
    printf("UDP -----\n");
    printf("Source port: %u\n", ntohs(udp->uh_sport));
    printf("Dest port: %u\n", ntohs(udp->uh_dport));
}

void analyseICMP(ICMP_HEADER *icmp)
{
    printf("ICMP -----\n");
    printf("type: %u\n", icmp->icmp_type);
    printf("sub code: %u\n", icmp->icmp_code);
}

--------------------- 本文来自 helloworldyu 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/yuhaiyang457288/article/details/78090673?utm_source=copy

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券