原文地址::https://blog.csdn.net/qq_33205982/article/details/54948250
相关文章
1、Linux网络编程——原始套接字实例:MAC 头部报文分析----https://blog.csdn.net/lianghe_work/article/details/45171713
2、Windows下利用原始套接字实现的一个抓包程序Demo----https://blog.csdn.net/kyt511/article/details/45950535
其实从大学学习了C语言后,翻看整本教材只有C语言的语法,根本没有网络编程相关的任何内容,现在回想起来,都记不起自己何时在哪本书上学习了套接字编程,说起TCP、UDP,能知道他们的区别,相关的编程的“套路”,即分别在服务器端和客户端应用程序的固定招数,函数也还是有那么多,但是功能方面嘛,仔细想想,一般除了通信传输数据,文件等,就没有其他目的了,即使有多播,组播,广播等,目的也是为了通信,而且都是在局域网内。以上提到的这些都太中规中矩了,总感觉少了些什么,比如网络性能监视类的程序,网络探测、网络攻击等程序肯定光用这个套接字编程技术无法实现。
写这篇文章是为了温故知新,照着Windows网络编程一书第7章内容而写。所以全手打。
-----------------------分----割----线-----------------------------------
原始套接字是允许访问底层传输协议的一种套接字类型,提供了普通套接字所不具备的功能,能够对网络数据包进行某种程度的控制操作。
原始套接字提供普通套接字不具备的能力有:发送和接收内核不处理其协议字段的IPv4数据包。对于8位IPv4协议字段,大多数内核仅仅处理该字段值为1(ICMP协议)、2(IGMP协议)、6(TCP协议)、17(UDP协议)的数据报,但是协议字段的值还有很多。例如,OSPF路由协议的值为89,如果要处理OSPF数据报文,那么程序必须使用原始套接字读写。
直接总结干货:
首先,在WinSock2中,很熟悉的两个函数:socket()和WSASocket(),第二个参数用常量SOCK_RAW指明,第三个参数IPPROTO_RAW或者IPPROTO_IP
其次,可以不用在原始套接字上调用bind()函数,也可以不用调用connect()函数,
然后,接收数据时可以调用recvfrom()或者WSARecvFrom()函数。
当接收数据时,在接收到一个数据包后,IP协议栈会把满足以下条件的IP数据包传递到原始套接字中:
1、既不是UDP的数据也不是TCP的数据包;2、部分ICMP分组;3、所有的IGMP分组;4、其他所有的IP数据包;5、重组后的分片数据。
传递到原始套接字中的数据包会根据接收条件决定是否拷贝,若满足则拷贝到原始套接字的接收缓冲区中。
假如我们希望能够接收到所有发给网卡的数据,甚至是流经网卡但并非给本机的数据,可以通过设置接收选项SIO_RCVALL就能实现,设置该套接字控制命令则需要通过函数WSAIoctl()。但是比如在设计一个ping程序时,发出请求后接收响应时,很可能有其他的ICMP消息产生干扰,此时就应该从数据来源IP地址,协议类型等方面来判断接收到的数据,正确匹配。
当然了,发送数据时可以调用sendto()或WSASendTo()函数,广播地址,多播地址同样适用(广播或者多播时需要通过setsocketopt()函数设置选项SO_BROADCAST!!)
connect()函数指明远端地址。
如果要设置IP首部,IPv4时选项为IP_HDRINCL,选项级别为IPPROTO_IP。当然,此后才能调用bind()函数来指明本机IP地址。要知道,只有这个选项开启了,才能在IP首部中修改源IP地址!可惜Windows对这些作了限制,如果源IP地址不正确,恶意代码不能通过伪造的源IP地址进行拒绝服务攻击,也不能发送IP欺骗数据包。如果原始套接字无法满足需求,还有WInPcap编程直接操控数据帧。
有两个编程示例:
第一个:使用原始套接字实现ping
ping是很多操作系统上用来检验主机是否连通网络,以及探测远程主机是否存活的实用工具之一,它通过系那个远程主机发送ICMP协议包并检验取回的远程主机吸纳供应包来实现上述功能。
概念: 位于网络层的IP协议和ICMP协议属于同等级的,
正因为原始套接字才能构造、发送和接收ICMP协议的数据包。ping程序使用了ICMP协议的ECHO类型的请求报文,程序中需要发送ECHO请求和接收响应,并计算间隔时间,判断网络状况。
ECHO请求由ICMP首部和ICMP数据组成,在前面加上IP首部构成IP数据包。即IP数据包=IP首部+ICMP首部+ICMP数据
所以,先定义ICMP首部的结构体,有类型,代码,校验和,ECHO请求的标识,序号5个成员共计8个字节
直接贴代码:
头文件:
#pragma pack(1)
#define ICMP_ECHOREPLY 0
#define ICMP_ECHOREQ 8
// IP首部结构体
typedef struct tagIPHDR
{
u_char VIHL; // 版本号和首部长度
u_char TOS; // 服务类型
short TotLen;// 总的长度
short ID;// 标识
short FlagOff;// 分片标志及偏移
u_char TTL; // TTL
u_char Protocol; // 协议
u_short Checksum; // 校验和
struct in_addr iaSrc; // 源IP地址
struct in_addr iaDst; // 目的IP地址
}IPHDR,*PIPHDR;
// ICMP首部结构体
typedef struct tagICMPHDR
{
u_char Type; // 类型(表示ECHO请求)
u_char Code; // 代码
u_short Checksum; // 校验和
u_short ID; // 标识
u_short Seq; // 序号
}ICMPHDR,*PICMPHDR;
#define REQ_DATASIZE 32 // Echo Request Data size
// ICMP ECHO请求结构体
typedef struct tagECHOREQUEST
{
ICMPHDR icmpHdr; //ICMP协议首部
DWORD dwTime; //获取的当前系统时间戳
char cData[REQ_DATASIZE]; //ICMP数据
}ECHOREQUEST,*PECHOREQUEST;
// ICMP ECHO应答结构体
typedef struct tagECHOREPLY
{
IPHDR ipHdr; //IP首部
ECHOREQUEST echoRequest; //ICMP请求的结构体
char cFiller[256]; //填充
}ECHOREPLY,*PECHOREPLY;
#pragma pack()
源文件:
#include <stdio.h>
#include <stdlib.h>
#include <winsock.h>
#include "ping.h"
void Ping(LPCSTR pstrHost);//调用了SendEchoRequest函数和RecvEchoReply函数。
void ReportError(LPCSTR pstrFrom);
int WaitForEchoReply(SOCKET s);
u_short in_cksum(u_short *addr,int len);
int SendEchoRequest(SOCKET,LPSOCKADDR_IN);
DWORD RecvEchoReply(SOCKET,LPSOCKADDR_IN,u_char *);
void main(int argc,char **argv)
{
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(1,1);
int nRet;
if (argc != 2)
{
fprintf(stderr,"\nUsage: ping hostname\n");
return;
}
nRet = WSAStartup(wVersionRequested,&wsaData);
if (nRet)
{
fprintf(stderr,"\nError initializing WinSock\n");
return;
}
if (wsaData.wVersion != wVersionRequested)
{
fprintf(stderr,"\nWinSock version not supported\n");
return;
}
Ping(argv[1]);
WSACleanup();
}
void Ping(LPCSTR pstrHost)
{
SOCKET rawSocket;
LPHOSTENT lpHost;
struct sockaddr_in saDest;
struct sockaddr_in saSrc;
DWORD dwTimeSent;
DWORD dwElapsed;
u_char cTTL;
int nLoop;
int nRet;
// 创建原始套接字,指定协议IPPROTO_ICMP
rawSocket = socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);
if (rawSocket == SOCKET_ERROR)
{
ReportError("socket()");
return;
}
// 获取IP地址
lpHost = gethostbyname(pstrHost);
if (lpHost == NULL)
{
fprintf(stderr,"\nHost not found: %s\n",pstrHost);
return;
}
saDest.sin_addr.s_addr = *((u_long FAR *) (lpHost->h_addr));
saDest.sin_family = AF_INET;
saDest.sin_port = 0;
printf("\nPinging %s [%s] with %d bytes of data:\n",
pstrHost,
inet_ntoa(saDest.sin_addr),
REQ_DATASIZE);
//连续ping4次
for (nLoop = 0; nLoop < 4; nLoop++)
{
SendEchoRequest(rawSocket,&saDest);//发送请求
nRet = WaitForEchoReply(rawSocket);//等待响应
if (nRet == SOCKET_ERROR)
{
ReportError("select()");
break;
}
if (!nRet)
{
printf("\nTimeOut");
break;
}
// 接收响应
dwTimeSent = RecvEchoReply(rawSocket,&saSrc,&cTTL);
// 计算间隔时间
dwElapsed = GetTickCount() - dwTimeSent;
printf("\nReply from: %s: bytes=%d time=%ldms TTL=%d",
inet_ntoa(saSrc.sin_addr),
REQ_DATASIZE,
dwElapsed,
cTTL);
}
printf("\n");
nRet = closesocket(rawSocket);
if (nRet == SOCKET_ERROR)
ReportError("closesocket()");
}
int SendEchoRequest(SOCKET s,LPSOCKADDR_IN lpstToAddr)
{
static ECHOREQUEST echoReq;
static nId = 1;
static nSeq = 1;
int nRet;
// 结构体成员赋值,填充echo请求
echoReq.icmpHdr.Type= ICMP_ECHOREQ;
echoReq.icmpHdr.Code= 0;
echoReq.icmpHdr.Checksum= 0;
echoReq.icmpHdr.ID= nId++;
echoReq.icmpHdr.Seq= nSeq++;
//填写数据
for (nRet = 0; nRet < REQ_DATASIZE; nRet++)
echoReq.cData[nRet] = ' '+nRet;
//获取当前时间并记录
echoReq.dwTime= GetTickCount();
//计算校验和
echoReq.icmpHdr.Checksum = in_cksum((u_short *)&echoReq,sizeof(ECHOREQUEST));
nRet = sendto(s,
(LPSTR)&echoReq,
sizeof(ECHOREQUEST),
0,
(LPSOCKADDR)lpstToAddr,
sizeof(SOCKADDR_IN));
if (nRet == SOCKET_ERROR)
ReportError("sendto()");
return (nRet);
}
DWORD RecvEchoReply(SOCKET s,LPSOCKADDR_IN lpsaFrom,u_char *pTTL)
{
ECHOREPLY echoReply;
int nRet;
int nAddrLen = sizeof(struct sockaddr_in);
nRet = recvfrom(s,
(LPSTR)&echoReply,
sizeof(ECHOREPLY),
0,
(LPSOCKADDR)lpsaFrom,
&nAddrLen);
if (nRet == SOCKET_ERROR)
ReportError("recvfrom()");
*pTTL = echoReply.ipHdr.TTL;
return(echoReply.echoRequest.dwTime);
}
void ReportError(LPCSTR pWhere)
{
fprintf(stderr,"\n%s error: %d\n",
WSAGetLastError());
}
int WaitForEchoReply(SOCKET s)
{
struct timeval Timeout;
fd_set readfds;
readfds.fd_count = 1;
readfds.fd_array[0] = s;
Timeout.tv_sec = 5;
Timeout.tv_usec = 0;
return(select(1,&readfds,NULL,&Timeout));
}
//
// Mike Muuss' in_cksum() function
// and his comments from the original
// ping program
//
// * Author -
// * Mike Muuss
// * U. S. Army Ballistic Research Laboratory
// * December,1983
/*
* I N _ C K S U M
*
* Checksum routine for Internet Protocol family headers (C Version)
*
*/
u_short in_cksum(u_short *addr,int len)
{
register int nleft = len;
register u_short *w = addr;
register u_short answer;
register int sum = 0;
/*
* Our algorithm is simple,using a 32 bit accumulator (sum),
* we add sequential 16 bit words to it,and at the end,fold
* back all the carry bits from the top 16 bits into the lower
* 16 bits.
*/
while( nleft > 1 ) {
sum += *w++;
nleft -= 2;
}
/* mop up an odd byte,if necessary */
if( nleft == 1 ) {
u_short u = 0;
*(u_char *)(&u) = *(u_char *)w ;
sum += u;
}
/*
* add back carry outs from top 16 bits to low 16 bits
*/
sum = (sum >> 16) + (sum & 0xffff);/* add hi 16 to low 16 */
sum += (sum >> 16);/* add carry */
answer = ~sum;/* truncate to 16 bits */
return (answer);
}
第二个:使用原始套接字实现的数据包捕获(极简单的统计流量,嗅探器)
int _tmain(int argc,_TCHAR* argv[]){WSADATA wsaData; SOCKET SnifferSocket = INVALID_SOCKET; char recvbuf[DEFAULT_BUFLEN]; int iResult; int recvbuflen = DEFAULT_BUFLEN;struct hostent *local; char HostName[DEFAULT_NAMELEN];struct in_addr addr;struct sockaddr_in LocalAddr,RemoteAddr;int addrlen = sizeof(struct sockaddr_in);int in=0,i=0;DWORD dwBufferLen[10]; DWORD Optval= 1 ; DWORD dwBytesReturned = 0 ;// 初始化套接字 //创建原始套接字 SnifferSocket = socket ( AF_INET,IPPROTO_IP);//获取本机名称iResult = gethostname( HostName,sizeof(HostName));//获取本机可用IP local = gethostbyname( HostName);printf ("\n本机可用的IP地址为:\n");while (local->h_addr_list[i] != 0) { addr.s_addr = *(u_long *) local->h_addr_list[i++];printf("\tIP Address #%d: %s\n",i,inet_ntoa(addr)); } printf ("\n请选择捕获数据待使用的接口号:");scanf_s( "%d",&in); memset( &LocalAddr,sizeof(LocalAddr));memcpy( &LocalAddr.sin_addr.S_un.S_addr,local->h_addr_list[in-1],sizeof(LocalAddr.sin_addr.S_un.S_addr));LocalAddr.sin_family = AF_INET;LocalAddr.sin_port=0;//绑定本地地址iResult = bind( SnifferSocket,(struct sockaddr *) &LocalAddr,sizeof(LocalAddr));//设置套接字接收命令iResult = WSAIoctl(SnifferSocket,SIO_RCVALL,&Optval,sizeof(Optval),&dwBufferLen,sizeof(dwBufferLen),&dwBytesReturned,NULL );//开始接收数据 printf(" \n开始接收数据");do{//接收数据iResult = recvfrom( SnifferSocket,recvbuf,DEFAULT_BUFLEN,(struct sockaddr *)&RemoteAddr,&addrlen);if (iResult > 0)printf ("\n接收到来自%s的数据包,长度为%d.",inet_ntoa(RemoteAddr.sin_addr),iResult );elseprintf("recvfrom Failed with error: %ld\n",WSAGetLastError());} while(iResult > 0);return 0;}