socket的精妙之处在于协议族的横向转换和地址族的纵向转换.我们也可在更底层实现对流经host的数据流的监督和修改.尤其是监察数据,十分简单.
这里是混杂模式实现对ip数据流的监察与对tcp数据流的简单查看,需要root权限.这里忽略了tcp/ip的options选项.进一步感兴趣的同学可以查看wareshark的源码实现.
尊重原创,转载的同学,一定要注明来自DNA出处哦!
#pragma once
#ifdef __linux__
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
#include <unistd.h>
#include <linux/if.h>
#include <linux/if_arp.h>
#include <sys/ioctl.h>
#include <alloca.h>
#endif
#define false 0
#define true 1
#define ETH_ALEN 6
#define isHumanized 0
#pragma pack(1)
//链路层MAC头部部分
struct ethHeader
{
unsigned char dst[6];//目的mac地址
unsigned char src[6];//源mac地址
unsigned short protocol;//上层(网络层)协议
};
struct ipHeader
{
unsigned char headerLen : 4;//0x45 0100 0101
unsigned char version :4;
unsigned char toservice:8;
unsigned short ipLen:16;
unsigned short sequence:16;
unsigned short segmentflag:16;
unsigned char time2live:8;
unsigned char protocol:8;
unsigned short checksum:16;
unsigned long sorceIp:32;
unsigned long destIp:32;
//unsigned char options[];
};
#ifdef BIGEND
//大端对齐的方式没试过,姑且按tcp表排序.
struct tcpHeader
{
unsigned short sorcePort:16;
unsigned short destPort:16;
unsigned long sequence:32;
unsigned long ack_seq:32;
unsigned char headerLen:4;
unsigned char residuum:6;
unsigned char urgent:1;
unsigned char acknowledge:1;
unsigned char push:1;
unsigned char reset:1;
unsigned char synchronize:1;
unsigned char final:1;
unsigned short window:16;
unsigned short checksum:16;
unsigned short urgentPoint:16;
unsigned char options[];
};
#else
struct tcpHeader
{
unsigned short sorcePort : 16;
unsigned short destPort : 16;
unsigned long sequence : 32;
unsigned long ack_seq : 32;
//网络:0100 ---- --00 0010
//主机:--00 0010 0100.
unsigned char residuum1 : 4; //----
unsigned char headerLen : 4; //0100
unsigned char final : 1; //0
unsigned char synchronize : 1; //1
unsigned char reset : 1; //0
unsigned char push : 1; //0
unsigned char acknowledge : 1; //0
unsigned char urgent : 1; //0
unsigned char residuum2 : 2; //--
unsigned short window : 16;
unsigned short checksum : 16;
unsigned short urgentPoint : 16;
unsigned char options[];
};
#endif
#pragma pack()
extern unsigned char print(unsigned char *buff,int len);
extern void printMyInfo(void);
struct ifreq ifstruct;
//网络字节序/大端对齐则true 小端对齐则false0x1234 低地址0x34-->高地址0x12
void testEnd()
{
union test_end
{
unsigned int get1;
unsigned char is1;
};
union test_end t;
t.get1 = 1;
if (t.is1)
{
printf("is1是低地址,在低位得到1,因此是小端对齐\n");
}
else
{
printf("is1是低地址,在低位没得1,因此是大端对齐\n");
}
}
int main(int argc,char *argv[])
{
//int socket(int domain,int type, int protocol)
//domain-----------:协议族 AF_INET AF_INET6 AF_LOCAL等
//type-------------:套接口类型 SOCK_STREAM(TCP) SOCK_DGRAM(UDP) SOCK_RAW(原始socket)
//wareshark :socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))
//ETH_P_ALL :该协议监听所有底层包,主机和网络字节序手动转换.
//vpn :socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP))
//ETH_P_IP :该协议监听所有ip包
//ping :socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)
//IPPROTO_ICMP :该协议监听所有ICMP包
//IPPROTO_IP(0) :该协议接收ip包
//IPPROTO_RAW(255) :该协议发送ip包,而不能接收
//UDP数据报 :socket(AF_INET, SOCK_DGRAM, 0)
//TCP数据流 :socket(AF_INET, SOCK_STREAM,0)
//AF address families PF protocol families
//socket()的fd只是存在于协议族的空间中.
//bind()分配一个指定的协议地址(网络字节序的ip与端口).
//客户端的ip和端口可以不用bind指定,而是在connect()时由系统生成.
//socket()的套接口默认未连接的主动套接口(需要connect的client),listen后成为被动套接口(内核将接受client对它的连接请求).
//未完成连接队列:处于SYN_RCVD 状态的套接口的集合
//已完成连接队列:处于ESTABLISHED状态的套接口的集合
//accetp:TCP服务器调用.从已完成连接队列返回下一个连接.队列为空则阻塞.第三个参数是输入输出参数len.
//send:每个tcp套接口都有自己的发送缓冲区,可以用SO_SNDBUF调节大小.内核将data拷贝到发送缓冲区:data若大于发送缓冲区,返回-1;data小于发送缓冲区,但大于剩余空间,且非阻塞,则拷贝一部分,若阻塞,则一直等待将所有数据拷贝至缓冲区才返回,因此阻塞返回值==len;若缓冲区满,且非阻塞,则等待.网络断开或错误,则返回-1.(TCP可靠:对方确认后才能删除发送缓冲区数据).
//sendto:UDP以数据报方式进行数据传输.因无连接,故需知名地址.因不可靠,故UDP没有自己的发送缓冲区,data被拷贝到内核缓冲区,链路层发送数据后删除拷贝.若无足够内核缓冲区,则返回ENOBUFS错误.
if (argc >= 2) {
testEnd(); return 0;
}
int fd = socket(AF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
char buff[4096];
memset(buff,0x0,4096);
int i = 0;
int n = 0;
struct sockaddr_ll sll;
memset(&ifstruct,0x0,sizeof(ifstruct));
memset(&sll,0x0,sizeof(sll));
strcpy(ifstruct.ifr_name,"eth0");//接口(网卡)名称
ioctl(fd,SIOCGIFINDEX,&ifstruct);//获取接口索引 SIOC:serial input/output controller
perror("ioctl获取SIOC索引");
sll.sll_family = AF_PACKET;//必须AF_PACKET族
sll.sll_ifindex = ifstruct.ifr_ifindex;//接口所在索引
sll.sll_protocol = htons(ETH_P_ALL);//物理层协议 任何协议
sll.sll_hatype = ARPHRD_ETHER;//硬件类型 以太
sll.sll_pkttype = PACKET_OTHERHOST;//包类型 混杂模式下发往其他host的包
sll.sll_halen = ETH_ALEN;//MAC地址长度 一般6
sll.sll_addr[6] = 0;//sll_addr[8] 物理层协议地址
sll.sll_addr[7] = 0;
ioctl(fd,SIOCGIFHWADDR,&ifstruct);//获取接口地址
perror("ioctl获取SIOC地址");
ioctl(fd,SIOCGIFFLAGS,&ifstruct);//获取接口flags
perror("ioctl获取SIOC标志");
ifstruct.ifr_flags |= IFF_PROMISC;
ioctl(fd,SIOCSIFFLAGS,&ifstruct);//设置为混杂模式
perror("ioctl设置混杂模式");
bind(fd,(struct sockaddr *)&sll,sizeof(struct sockaddr_ll));//绑定
perror("bind尝试将获取以及设置生效");
printMyInfo();
while(true)
{
memset(buff,0x0,4096);
n = read(fd,buff,1024);
if(n < 0) perror("读取物理层数据");
print((unsigned char *)buff,n);
}
printf("\nend!\n");
}
char *inet_htoa(unsigned long ipaddr)//c0 a8 00 45
{
if(ipaddr == 0) return NULL;
static char buff[15] = {0};
char *p = buff;
unsigned char flag = false;
unsigned char num = (ipaddr >> 24) & 0xff;//c0
*p = num / 100;//1.92 = 1
if(*p != 0){
flag = true;
*p++ += 0x30; // '1'
}
*p = (num / 10) % 10;//19.2 => 19 => 9
if(*p != 0 || flag == true) {
*p++ += 0x30;// '9'
}
flag = false;
*p++ = num % 10 + 0x30;// '2'
*p++ = '.';
num = (ipaddr >> 16) & 0xff;//a8
*p = num / 100;
if(*p != 0){
flag = true;
*p++ += 0x30;
}
*p = (num / 10) % 10;
if(*p != 0 || flag == true){
*p++ += 0x30;
}
flag = false;
*p++ = num % 10 + 0x30;
*p++ = '.';
num = (ipaddr >> 8) & 0xff;//a8
*p = num / 100;
if(*p != 0){
flag = true;
*p++ += 0x30;
}
*p = (num / 10) % 10;
if(*p != 0 || flag == true){
*p++ += 0x30;
}
flag = false;
*p++ = num % 10 + 0x30;
*p++ = '.';
num = ipaddr & 0xff;//a8
*p = num / 100;
if(*p != 0){
flag = true;
*p++ += 0x30;
}
*p = (num / 10) % 10;
if(*p != 0 || flag == true){
*p++ += 0x30;
}
*p++ = num % 10 + 0x30;
*p = 0;
return buff;
}
char *judge_proto(unsigned char c)
{
static char array[5] = "0x";
switch(c){
case 0x01:
return "0x01:ICMP";
case 0x02:
return "0x02:IGMP";
case 0x06:
return "0x06:TCP";
case 0x11:
return "0x11:UDP";
case 0x84:
return "0x84:SCTP";
default:
array[2] = ((c >> 4) & 0x0f) + 0x30;
array[3] = (c & 0x0f) + 0x30;
array[4] = '\0';
return array;
}
}
//#define BINARYDATA
unsigned char print(unsigned char *buff,int n)
{
int i = 0;
if(n < 0) {printf("recv err.\n");return -1;}
if(n == 0){printf("二进制数据流为0\n");return -1;}
//#define BINARYDATA
#ifdef BINARYDATA
#if isHumanized
printf("this is binary!\n");
if(n > 0) {
printf("\n------------二进制数据流开始----------\n");
while(n-- >= 0)
{
printf("%02x ",buff[i++]);
}
printf("\n------------二进制数据流结束----------\n");
}
printf("解析二进制数据流......\n");
printf("数据流接收端MAC:[%02x:%02x:%02x:%02x:%02x:%02x]\n",
buff[0],buff[1],buff[2],buff[3],buff[4],buff[5]);
printf("数据流发送端MAC:[%02x:%02x:%02x:%02x:%02x:%02x]\n",
buff[6],buff[7],buff[8],buff[9],buff[10],buff[11]);
if(ntohs(*(unsigned short *)&buff[12]) != 0x0800) {
goto END;
}
else {
printf("解析到ipv4包.正在进一步解析......\n");
}
#else
if (n > 0) {
printf("\n------------二进制数据流开始----------\n");
while (n-- >= 0)
{
printf("%02x ", buff[i++]);
}
printf("\n------------二进制数据流结束----------\n");
}
printf("解析二进制数据流......\n");
struct ethHeader * ethheader = (struct ethHeader *)buff;
printf("数据流接收端MAC:[%02x:%02x:%02x:%02x:%02x:%02x]\n",
ethheader->dst[0],
ethheader->dst[1],
ethheader->dst[2],
ethheader->dst[3],
ethheader->dst[4],
ethheader->dst[5]);
printf("数据流发送端MAC:[%02x:%02x:%02x:%02x:%02x:%02x]\n",
ethheader->src[6],
ethheader->src[7],
ethheader->src[8],
ethheader->src[9],
ethheader->src[10],
ethheader->src[11]);
if (ntohs(*(unsigned short *)&buff[12]) != 0x0800) {
goto END;
}
else {
printf("解析到ipv4包.正在进一步解析......\n");
}
#endif //BINARYDATA
#endif
if((buff[14] & 0xf0) != 0x40) return -1;
#if isHumanized
printf("ip首部长度:%u\n",buff[14] & 0x0f);
printf("ip服务类型:%u\n",buff[15]);
printf("ip总长度 :%u\n",ntohs(*(unsigned short *)&buff[16]));
printf("ip标识序列:%u\n",ntohs(*(unsigned short *)&buff[18]));
printf("ip分片偏移:%u\n",ntohs(*(unsigned short *)&buff[20]));
printf("ip生存时间:%u\n",buff[22]);
printf("ip上层协议:%s\n",judge_proto(buff[23]));
printf("ip校验和 :%u\n",ntohs(*(unsigned short *)&buff[24]));
printf("ip源地址 :%s\n",inet_htoa(ntohl(*(unsigned long *)&buff[26])));
printf("ip目的地址:%s\n",inet_htoa(ntohl(*(unsigned long *)&buff[30])));
#else
struct ipHeader *ipheader = (struct ipHeader *)&buff[14];
printf("ip首部长度:%u\n", ipheader->headerLen);
printf("ip服务类型:%u\n", ipheader->toservice);
printf("ip总长度 :%u\n", ntohs(ipheader->ipLen));
printf("ip标识序列:%u\n", ntohs(ipheader->sequence));
printf("ip分片偏移:%u\n", ntohs(ipheader->segmentflag));
printf("ip生存时间:%u\n", ipheader->time2live);
printf("ip上层协议:%s\n", judge_proto(ipheader->protocol));
printf("ip校验和 :%u\n", ntohs(ipheader->checksum));
printf("ip源地址 :%s\n", inet_htoa(ntohl(ipheader->sorceIp)));
printf("ip目的地址:%s\n", inet_htoa(ntohl(ipheader->destIp)));
#endif
if(buff[23] != 0x06) goto END;
#if isHumanized
printf("解析到tcp包,正在进一步解析\n");
printf("tcp源端口 :%u\n",ntohs(*(unsigned short *)&buff[34]));
printf("tcp目的端口 :%u\n",ntohs(*(unsigned short *)&buff[36]));
printf("tcp标识序列 :%u\n",ntohs(*(unsigned long *)&buff[38]));
printf("tcp确认序列 :%u\n",ntohs(*(unsigned long *)&buff[42]));
printf("tcp首部长度 :%u\n",(buff[46] >> 4) & 0x0f);
printf("tcp紧急标识urg:%u\n",(buff[47] >> 5) & 0x01);
printf("tcp回应标识ack:%u\n",(buff[47] >> 4) & 0x01);
printf("tcp请勿缓存psh:%u\n",(buff[47] >> 3) & 0x01);
printf("tcp重置连接rst:%u\n",(buff[47] >> 2) & 0x01);
printf("tcp建立连接syn:%u\n",(buff[47] >> 1) & 0x01);
printf("tcp释放连接fin:%u\n",(buff[47] & 0x01));
printf("tcp滑动窗口win:%u\n",ntohs(*(unsigned short *)&buff[48]));
printf("tcp校验和 :%u\n",ntohs(*(unsigned short *)&buff[50]));
printf("tcp紧急指针 :%u\n",ntohs(*(unsigned short *)&buff[52]));
int len = ntohs(*(unsigned short *)&buff[16]) - 4 * (buff[14] & 0x0f) - 4 * ((buff[46] >> 4) & 0x0f);
printf("tcp字段的数据长度(单位:bytes):%d\n",len);
if(len == 0) {goto END;}
printf("开始解析tcp数据报,将以ASCII形式解析......\n");
for(i = 54; i < len + 54; ++i)
{
printf("%c",buff[i]);
}
#else
struct tcpHeader *tcpheader = (struct tcpHeader *)&buff[14 + 4 * (ipheader->headerLen)];
//struct tcpHeader *tcpheader = (struct tcpHeader *)malloc(sizeof(struct tcpHeader));
//memcpy(tcpheader, &buff[14 + 4 * (ipheader->version_ihl)],sizeof(struct tcpHeader));
printf("解析到tcp包,正在进一步解析\n");
printf("tcp源端口 :%u\n", ntohs(tcpheader->sorcePort));
printf("tcp目的端口 :%u\n", ntohs(tcpheader->destPort));
printf("tcp标识序列 :%u\n", ntohs(tcpheader->sequence));
printf("tcp期望序列 :%u\n", ntohs(tcpheader->ack_seq));
printf("tcp首部长度 :%u\n", (tcpheader->headerLen));
printf("tcp紧急标识urg:%u\n", tcpheader->urgent);
printf("tcp回应标识ack:%u\n", tcpheader->acknowledge);
printf("tcp请勿缓存psh:%u\n", tcpheader->push);
printf("tcp重置连接rst:%u\n", tcpheader->reset);
printf("tcp建立连接syn:%u\n", tcpheader->synchronize);
printf("tcp释放连接fin:%u\n", tcpheader->final);
printf("tcp滑动窗口win:%u\n", ntohs(tcpheader->window));
printf("tcp校验和 :%u\n", ntohs(tcpheader->checksum));
printf("tcp紧急指针 :%u\n", ntohs(tcpheader->urgentPoint));
unsigned int len = ntohs(ipheader->ipLen) - 4 * (ipheader->headerLen) - 4 * (tcpheader->headerLen);
if (len == 0) { goto END; }
printf("开始解析tcp数据报,将以ASCII形式解析......\n");
char *data = (char *)alloca(len + 1);
strncpy(data, tcpheader->options, len);
data[len] = 0;
int tmp;
for (i = 0; i < len; i+=tmp)
{
tmp = printf("%s\n", &data[i]);
}
sleep(1);
#endif
printf("\n");
END:
printf("解析结束.\n\n");
return 0;
}
void printMyInfo()
{
printf("本机网卡名称:\"%s\",本机MAC地址:[%02x:%02x:%02x:%02x:%02x:%02x]\n",
ifstruct.ifr_name,
(unsigned char)(ifstruct.ifr_hwaddr.sa_data[0]),
(unsigned char)(ifstruct.ifr_hwaddr.sa_data[1]),
(unsigned char)(ifstruct.ifr_hwaddr.sa_data[2]),
(unsigned char)(ifstruct.ifr_hwaddr.sa_data[3]),
(unsigned char)(ifstruct.ifr_hwaddr.sa_data[4]),
(unsigned char)(ifstruct.ifr_hwaddr.sa_data[5]));
}