原始套接字 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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章