原始套接字 TCP UDP 数据包详解 分析 MAC 数据包 sendto 发送数据 飞鸽欺骗(UDP) 三次握手连接器(TCP)

                                           粉丝不过W 

TCP、UDP 开发回顾

   数据报式套接字(SOCK_DGRAM)

        无连接的 socket,针对无连接的 UDP 服务

        与邮件模型来进行对比

   流式套接字(SOCK_STREAM)

      面向连接的 socket,针对面向连接的 TCP 服务

     与电话模型来进行对比

这两类套接字似乎涵盖了 TCP/IP 应用的全部

  TCP 与 UDP 各自有独立的 port 互不影响

    一个进程同时可拥有多个 port

   不用关心 tcp/ip 协议实现的过程

UDP 编程回顾

   client

     创建 socket 接口

     定义 sockaddr_in 变量,其中 ip、port 为目的主机的信息

     可发送 0 长度的数据包

  server

     bind 本地主机的 ip、port 等信息

     接收到的数据包中包含来源主机的 ip、port 信息

TCP 编程回顾

   client

    connect 来建立连接

    write、read 收发数据

    不可发送 0 长度的数据

  server

     bind 本地主机的 ip、port 等信息

     listen 把主动套接字变为被动

      accept 会有新的返回值

      多进程、线程完成并发

 原始套接字概述、创建

     原始套接字概述

          原始套接字(SOCK_RAW)

             一种不同于 SOCK_STREAM、SOCK_DGRAM 的套接字,它实现于系统核心

            可接收本机网卡上所有的数据帧(数据包),对于监听网络流量和分析网络数据很有作用

            开发人员可发送自己组装的数据包到网络上

            广泛应用于高级网络编程

            网络专家、黑客通常会用此来编写奇特的网络程序

      流式套接字只能收发

          TCP 协议的数据

     数据报套接字只能收发

         UDP 协议的数据

     原始套接字可以收发

         内核没有处理的数据包,因此要访问其他协议

         发送的数据需要使用,原始套接字(SOCK_RAW)

  创建原始套接字

/*
 *function:
 *  创建链路层的原始套接字
 *parameter:
 *  protocol:指定可以接收或发送的数据包类型
 *    ETH_P_IP:  IPV4数据包
 *    ETH_P_ARP: ARP数据包
 *    ETH_P_ALL: 任何协议类型的数据包
 *return:
 *  成功(>0):链路层套接字
 *  失败(<0):出错
 */
int socket(PF_PACKET, SOCK_RAW, protocol);

   创建链路层的原始套接字

#include <sys/socket.h>
#include <netinet/ether.h>

sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

  数据包详解

      在 TCP/IP 协议栈中的每一层为了能够正确解析出上层的数据包,从而使用一些“协议类型”来标记

   组装/拆解 udp 数据包流程

UDP 封包格式

IP 封包格式

Ethernet 封包格式

TCP 封包格式

 ICMP 封包格式

      ICMP 回显请求和回显应答格式

      不同的类型值以及代码值,代表不同的功能

分析 MAC 数据包

   链路层数据格式

#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netinet/ether.h>

int main(int argc, char *argv[])
{
    unsigned char buf[1024] = "";
    //创建链路层原始套接字
    int sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    
    while(1)
    {
        unsigned char src_mac[18] = "";
        unsigned char dst_mac[18] = "";
        //获取链路层的数据帧
        recvfrom(sock_raw_fd, buf, sizeof(buf), 0, NULL, NULL);
        //根据格式解析数据
        //从buf里提取目的mac、源mac
        sprintf(dst_mac, "%02x:%02x:%02x:%02x:%02x:%02x",
                        buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
        sprintf(src_mac, "%02x:%02x:%02x:%02x:%02x:%02x",
                        buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]);
        //打印源MAC、目的MAC
        printf("MAC:%s >> %s\n", src_mac, dst_mac);
    }
    return 0;
}

网络数据分析图

ARP 数据解析图

     ARP 的 TYPE 为 0x0806

     buf 为 unsinged char

     所有数据均为大端

IP 数据解析图

   IP 的 TYPE 为 0x0800

   buf 为 unsinged char

   所有数据均为大端

  如下,网上的数据包的组包过程;其解包过程正好相反,首先分析以太网得到 MAC 然后再依次分析,如 IP、PORT 

混杂模式

   混杂模式

      指一台机器的网卡能够接收所有经过它的数据包,而不论其目的地址是否是它

      一般计算机网卡都工作在非混杂模式下,如果设置网卡为混杂模式需要 root 权限

linux 下设置

//设置混杂模式
ifconfig eth0 promisc
//取消混杂模式
ifconfig eth0 -promisc

linux 下通过程序设置网卡混杂模式:

struct ifreq ethreq;
strncpy(ethreq.ifr name, "eth0", IFNAMSIZ);

//获取eth0网络接口标志
if(ioctl(sock_raw_fd, SIOCGIFFLAGS, &ethreq) != 0)
{
    perror("ioctl");
    close(sock_raw_fd);
    exit(-1);
}

ethreq.ifr_flags |= IFF_PROMISC;
//设置eth0网络接口标志
if(ioctl(sock_raw_fd, SIOCSIFFLAGS, &ethreq) != 0)
{
    perror("ioctl");
    close(sock_raw_fd);
    exit(-1);
}

sendto 发送数据

   用 sendto 发送原始套接字数据

/*
 *sock_raw_fd:原始套接字
 *msg:         发送的消息(封装好的协议数据)
 *sll:         本机网络接口,指发送的数据应该从本机的哪个网卡出去,而不是以前的目的地址
 */
sendto(sock_raw_fd, msg, msg_len, 0, (struct sockaddr *)&sll, sizeof(sll));

本机网络接口

#include <netpacket/packet.h>

struct sockaddr_ll
{
    unsigned short int sll_family;      //一般为PF_PACKET
    unsigned short int sll_protocol;    //上层协议
    int                sll_ifindex;     //接口类型
    unsigned short int sll_hatype;      //报头类型
    unsigned char sll_pkttype;          //包类型
    unsigned char sll_halen;            //地址长度
    unsigned char sll_addr[8];          //MAC地址
}

   只需要对 sll.sll_ifindex 赋值,就可使用

  发送数据 demo

//将网络接口赋值给原始套接字地址结构
struct sockaddr_ll all;
bzero(&sll, sizeof(sll));

sll.sll_ifindex = /* 获取本机的出去接口地址 */

int len = sendto(sock_raw_fd, msg, sizeof(msg), 0, (struct sockaddr *)&sll, sizeof(sll));

通过 ioctl 来获取网络接口地址

#include <sys/ioctl.h>

int ioctl(int fd, int request, void *);

ioctl 获取接口示例

/* 网络接口地址 */
struct ifreq ethreq;
/* 指定网卡名称 */
strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ);
/* 获取网络接口 */
if( -1 == ioctl(sock_raw_fd, SIOCGIFINDEX, &ethreq))
{
    perror("ioctl");
    close(sock_raw_fd);
    exit(-1);
}

struct sockfaddr_ll sll;
bzero(&sll, sizeof(sll));
//给sll赋值
sll.sll_ifindex = ethreq.ifr_ifindex;
//发送
int len = sendto(sock_raw_fd, msg, sizeof(msg), 0, (struct sockaddr *)&sll, sizeof(sll));

ioctl 参数对照表

类别 request 说明 数据类型
接口 SIOCGIFINDEX 获取网络接口 struct ifreq
SIOCSIFADDR 设置接口地址 struct ifreq
SIOCGIFADDR 获取接口地址 struct ifreq
SIOCSIFFLAGS 设置接口标志 struct ifreq
SIOCGIFFLAGS 获取接口标志 struct ifreq
#include <net/if.h>
#define IFNAMSIZ    16
//网络接口地址
struct ifreq ethreq;
//指定网卡名称
strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ);
//获取网络接口
ioctl(sock_raw_fd, SIOCGIFINDEX, &ethreq);

ARP 概述

    ARP(Address Resolution Protocol,地址解析协议)

         TCP/IP 协议族中的一个

         主要用于查询指定 ip 所对应的的 MAC

         请求方使用广播来发送请求

        应答方使用单播来回送数据

        为了在发送数据的时提高效率 在计算中会有一个 ARP 缓存表,用来暂时存放 ip 所对应的 MAC,在 linux中使用 ARP 即可查看

  机器 A 获取机器 B 的 MAC :

  ARP 协议格式

向指定 IP 发送 ARP 请求(demo)

int main(int argc, char *argv[])
{
    //创建通信用的原始套接字
    int sock_raw_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

    //根据各种协议首部格式构造发送数据报
    unsigned char send_msg[1024] = {
			/****** 组MAC   14 *********/
			0xff,0xff,0xff,0xff,0xff,0xff,	//dst_mac:FF:FF:FF:FF:FF:FF
			0x00,0x0c,0x29,0x75,0xa6,0x51,	//src_man:00:0c:29:75:a6:51
			0x08,0x06,	//类型:0x0806 ARP协议
			/******* 组ARP    28 *******/
			0x00,0x01,0x08,0x00,	      //硬件类型1(以太网地址),协议类型0x0800(ip)
			0x06,0x04,0x00,0x01,	      //硬件,协议地址分为6,4 op:(1:arp请求 2:arp应答)
			0x00,0x0c,0x29,0x75,0xa6,0x51,	//发送端的MAC地址
			172, 20,  226, 12,	            //发送端的IP地址
			0x00,0x00,0x00,0x00,0x00,0x00,	//目的MAC地址(获取对方MAC,目的MAC置0)
			172,20,226,11
                                   };
	//数据初始化
	struct sockaddr_ll sll;	//原始套接字地址结构
	struct ifreq ethreq;	//网络接口地址

	strncpy(ethreq.ifr_name, "eth0", IFNAMSIZ);	//指定网卡名称

	//将网络接口赋值给原始套接字地址结构
	ioctl(sock_raw_fd, SIOCGIFINDEX, (char *)&ethreq);

	bzero(&sll, sizeof(sll));
	sll.sll_ifindex = ethreq.ifr_ifindex;
	sendto(sock_raw_fd, send_msg, 42, 0, (struct sockaddr *)&sll, sizeof(sll));

	//接收对方的ARP应答
	unsigned char recv_msg[1024] = "";
	recvfrom(sock_raw_fd, recv_msg, sizeof(recv_msg), 0, NULL, NULL);

	if(recv_msg[21] == 2)
	{
		char resp_mac[18] = "";	//arp响应的MAC
		char resp_ip[16] = "";	//arp响应的IP

		sprintf(resp_mac, "%02x:%02x:%02x:%02x:%02x:%02x",
							recv_msg[22], recv_msg[23],
							recv_msg[24], recv_msg[25],
							recv_msg[26], recv_msg[27]);
		sprintf(resp_ip, "%d.%d.%d.%d",
							recv_msg[28], recv_msg[29],
							recv_msg[30], recv_msg[31]);
		printf("IP:%s - MAC:%s\n",resp_ip, resp_mac);
	}

	return 0;
}

 飞鸽欺骗(UDP)

飞鸽格式:
     版本:用户名:主机名:命令字:附加消息

 组包过程:

/*
 *飞鸽消息格式:
 *note:
 *  msg : udp 报文头中的数据
 */
sprintf(msg, "1:%d:%s:%s:%d:%s", 123, "qfedu", "qfedu", 32, ok);

MAC、IP、UDP 报文头参考前面的数据包详解

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <netinet/udp.h>
#include <arpa/inet.h>
#include <unistd.h>

unsigned short checksum(unsigned short *buf, int nword);

int main(int argc, char *argv[])
{
    int sockfd = 0;
    struct sockaddr_in dest_addr;
    //组织发送的信息;注意伪头部
    char udp_checksum_buf[] = {
        /****** 伪头部 开始 ******/
        172, 20, 223, 119,  //src ip
        172, 20, 223, 83,   //dst ip
        0x00,               //默认
        17,                 //udp
        0x00, 34,           //udp总长度(header lenth + udp data length)
        /****** 伪头部-结束 ******/
        /****** udp   首部 ******/
        0x09, 0x79,			//udp src port,2425(feiQ)
        0x09, 0x79,			//udp dst port,2425(feiQ)
        0x00, 34,			//udp总长度
        0x00, 0x00,			//udp data checksum,注意 校验和不计算也是可以的但是必须为0
        '1', ':',			//1 代表飞秋的版本号
        '1', '2', '3', ':',	//123 本次发送的飞秋的数据包的报编号
        't', 'o', 'm', ':',	//tom 发送者姓名
        'q', 'f', ':',	    //sun 发送者主机名
        '1', ':',			//1 代表上线
        'q', 'f', 'e', 'd', 'u'     //qfedu 发送者的名字
    };

    //创建 网络层原始套接字,并且指定将来作用udp相关
    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
    if(sockfd < 0)
    {
        perror("socket error");
        exit(1);
    }

    //配置 结构体变量,代表着目的主机的信息
    bzero(&dest_addr, sizeof(dest_addr));   //初始化
    //套接字域AF_INET(网络套接字)
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(2425);
    //设置目的IP
    dest_addr.sin_addr.s_addr = inet_addr("172.20.223.83");

    //对发送的信息进行校验
    *((unsigned short *)&udp_checksum_buf[18]) = htons(checksum((unsigned short *)udp_checksum_buf, 
                                                                 sizeof(udp_checksum_buf)/2));
    //发送数据到指定目的
    sendto(sockfd, udp_checksum_buf+12, 34, 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr));

    //关闭套接字
    close(sockfd);
    return 0;
}

/*
 *function:
 *  对数据进行校验
 *note:
 *  注意伪头部的问题
 */
unsigned short checksum(unsigned short *buf, int nword)
{
    unsigned long sum;

    for(sum = 0; nword > 0; nword--)
    {
        sum += htons(*buf);
        buf++;
    }
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);

    return ~sum;
}

三次握手连接器(TCP)

   TCP 数据包(发送一个 SYN 数据包)

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>

unsigned short checksum(unsigned short *buf, int nword);

/*
 *function:
 *  创建原始套接字,然后手动组织tcp数据包,然后发送
 */
int main(int argc, char *argv[])
{
    int sockfd = 0;
    struct sockaddr_in dest_addr;
    /*组织发送的信息;
	 *note:
	 *	伪头部
	 *	0x50的高4位代表是首部长度,要注意其代表的是4Byte的个数,即0x50代表的首部长度为20(5*4)
	 *	本次发送的tcp数据包,只有tcp包头,不包含任何tcp数据
     */
    char tcp_checksum_buf[] = {
        /******* 伪头部 开始 ******/
        172, 20, 223, 119,			//src ip
        172, 20, 223, 83,			//dst ip
        0x00,					    //默认
        6,					    	//6 为tcp
        0x00, 20,				//tcp 头部长度
        /******* 伪头部 结束 ******/
        /******* TCP头部 开始 *****/
        0x55, 0x22,				//tcp src port
        0x00, 80,				//tcp dst port,port=80
        0x00, 0x00, 0x00, 0x01,	//tcp id
        0x00, 0x00, 0x00, 0x00,	//tcp ack
        0x50, 0x02, 0x17, 0x70,	//4位首部长度+6位保留+6个标志位(URG/ACK/PSH/PST/SYN/FIN)+16位窗口大小
        0x00, 0x00,				//16位tcp校验和
        0x00, 0x00				//16位紧急指针
        /******* TCP头部 结束 ******/
    };

    //创建 网络层原始套接字,并且指定将来作用tcp相关
    sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
    if(sockfd < 0)
    {
        perror("socket error");
        //exit(1):异常退出
        //exit(0):正常退出
        exit(1);
    }
    //配置 结构体变量,代表着目的主机的信息
    bzero(&dest_addr, sizeof(dest_addr));    //初始化
    //套接字域是AF_INET(网络套接字)
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(80);
    //设置目的ip
    dest_addr.sin_addr.s_addr = inet_addr("172.20.223.83");

    // 对发送的信息进行校验
    *((unsigned short *)&tcp_checksum_buf[28]) = htons(checksum((unsigned short *)tcp_checksum_buf, 
                                                                sizeof(tcp_checksum_buf)/2));
    // 发送数据到指定目的
    sendto(sockfd, tcp_checksum_buf + 12, 20, 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr));

    //关闭套接字
    close(sockfd);
	return 0;
}

/*
 *function:
 *  对数据进行校验
 *note:
 *  注意伪头部的问题
 */
unsigned short checksum(unsigned short *buf, int nword)
{
    unsigned long sum;

    for(sum = 0; nword > 0; nword--)
    {
        sum += htons(*buf);
        buf++;
    }
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);

    return ~sum;
}

猜你喜欢

转载自blog.csdn.net/qq_44226094/article/details/105676051