粉絲不過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, ðreq) != 0)
{
perror("ioctl");
close(sock_raw_fd);
exit(-1);
}
ethreq.ifr_flags |= IFF_PROMISC;
//設置eth0網絡接口標誌
if(ioctl(sock_raw_fd, SIOCSIFFLAGS, ðreq) != 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, ðreq))
{
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, ðreq);
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 *)ðreq);
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;
}