原文地址::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;}

Windows平台的原始套接字编程的知识点概要备忘的更多相关文章

  1. ios – 确定核心音频AudioBuffer中的帧数

    我正在尝试访问iPhone/iPad上的音频文件的原始数据.我有以下代码,这是我需要的路径的基本开始.但是,一旦我有了一个AudioBuffer,我就不知道该怎么做了.基本上我不知道如何判断每个缓冲区包含多少帧,因此我无法从它们中可靠地提取数据.我是处理原始音频数据的新手,所以我对如何最好地读取AudioBuffer结构的mData属性有任何建议.我在过去也没有做过很多关于void指针的事情,所以在这种情况下对它的帮助也会很棒!

  2. iOS – 生成并播放无限简单的音频(正弦波)

    我正在寻找一个非常简单的iOS应用程序,它带有一个启动和停止音频信号的按钮.信号只是一个正弦波,它将在整个播放过程中检查我的模型,并相应地改变音量.我的困难与任务的不确定性有关.我理解如何构建表格,填充数据,响应按钮按下等等;然而,当谈到只是无限期地继续时,我有点卡住了!任何指针都会很棒!

  3. 配置iOS VoIP应用程序以在睡眠/后台模式下运行

    我正在开发基于VoIP的iOS(7.1)应用程序.它的底层套接字编程是用C而不是客观C编写的.应用程序在前台运行良好,但在进入睡眠/后台模式时,它无法从服务器接收任何通信.根据apple文档,我们必须为VoIP使用配置一个appsocket.我无法弄清楚如何配置C套接字.目的是在睡眠模式下运行应用程序,直到它被杀死.从SO中尝试了几个链接甚至几个链接,但由于我是新手,我希望这个配置有一步一步的过程.[注意:在某个地方我发现了CoreFoudation框架,我是否需要使用它?

  4. 我应该使用哪个高级API来管理iOS上的UDP套接字?

    在“NetworkProgrammingTopicsConceptualGuide”的“UsingSocketsandStreams”一章中,Apple说:Note:POSIXnetworkingdoesnotactivatethecellularradiooniOS.Forthisreason,thePOSIXnetworkingAPIisgenerallydiscouragediniOS.同样

  5. iOS:使用CFStreamCreatePairWithSocketToHost的套接字网络基础

    >每次要发送新数据对象时,是否设置了新套接字?>我是否必须重置outputStream并发送更多数据.码大部分代码来自CocoaStreamsDocumentation:响应:请注意,在发送数据后,outputStream流将关闭.我尝试在[selfsendString:@“AnotherTest”]之前重新启动outputStream.我也试过了idz的回答.根据文档,我相信len:0的发送缓冲区是我的问题.IfthedelegatereceivesanNsstreamEventHasspaceAvai

  6. ios – 核心音频离线渲染GenericOutput

    等正在产生这些问题.尝试努力,它会工作.不要放弃:-).核心音频在处理低级音频时非常强大和有用.这是我从最近几周学到的东西.享受:-D…

  7. ios – AFNetworking / NSURLConnection接收NSPOSIXErrorDomain代码= 9“操作无法完成.坏文件描述符“

    有人在他们的AFNetworking操作中遇到这个错误吗?此外,如果我真的想要,如何故意关闭这个文件描述符?

  8. 如何正确使用iOS(Swift)SceneKit SCNSceneRenderer unprojectPoint

    那么,如果那架飞机与摄像机是正交的–那就是你的帮助.那么你需要做的就是在那架飞机上投射一点:现在,您可以在三维视图中拥有世界起源的位置归一化深度空间.要将2D视图空间中的其他点映射到此平面上,请使用此矢量中的z坐标:这让您在世界空间中将点击/分接位置映射到z=0平面,适合用作节点的位置,如果要向用户显示该位置.

  9. iOS:无法完成套接字错误操作断管

    我每隔一段时间就会从iOS应用程序向套接字服务器发送一些数据.它正确地发送数据.我看到一个奇怪的问题,如果iOS设备屏幕在从iOS应用程序发送数据时关闭,然后如果我在设备上屏幕,然后我收到以下错误,应用程序已经与套接字断开连接或有时它会崩溃应用程序:当设备屏幕关闭时,我的iOS应用程序停止向套接字发送数据.然后再打开屏幕,插座连接断开/断开管道错误.怎么解决?

  10. Swift:如何使用sizeof?

    为了在使用Swift时与CAPI集成,我需要使用sizeof函数。在C,这很容易。在Swift,我在一个迷宫式的错误。为什么是这个,我如何解决它?如果你想要anInt变量的大小,你可以将dynamicType字段传递给sizeof。

随机推荐

  1. static – 在页面之间共享数据的最佳实践

    我想知道在UWP的页面之间发送像’selectedItem’等变量的最佳做法是什么?创建一个每个页面都知道的静态全局变量类是一个好主意吗?

  2. .net – 为Windows窗体控件提供百分比宽度/高度

    WindowsForm开发的新手,但在Web开发方面经验丰富.有没有办法为Windows窗体控件指定百分比宽度/高度,以便在用户调整窗口大小时扩展/缩小?当窗口调整大小时,可以编写代码来改变控件的宽度/高度,但我希望有更好的方法,比如在HTML/CSS中.在那儿?

  3. 使用Windows Azure查询表存储数据

    我需要使用特定帐户吗?>将应用程序部署到Azure服务后,如何查询数据?GoogleAppEngine有一个数据查看器/查询工具,Azure有类似的东西吗?>您可以看到的sqlExpressintance仅在开发结构中,并且一旦您表示没有等效,所以请小心使用它.>您可以尝试使用Linqpad查询表格.看看JamieThomson的thispost.

  4. windows – SetupDiGetClassDevs是否与文档中的设备实例ID一起使用?

    有没有更好的方法可以使用DBT_DEVICEARRIVAL事件中的数据获取设备的更多信息?您似乎必须指定DIGCF_ALLCLASSES标志以查找与给定设备实例ID匹配的所有类,或者指定ClassGuid并使用DIGCF_DEFAULT标志.这对我有用:带输出:

  5. Windows Live ID是OpenID提供商吗?

    不,WindowsLiveID不是OpenID提供商.他们使用专有协议.自从他们的“测试版”期结束以来,他们从未宣布计划继续它.

  6. 如果我在代码中进行了更改,是否需要重新安装Windows服务?

    我写了一个Windows服务并安装它.现在我对代码进行了一些更改并重新构建了解决方案.我还应该重新安装服务吗?不,只需停止它,替换文件,然后重新启动它.

  7. 带有双引号的字符串回显使用Windows批处理输出文件

    我正在尝试使用Windows批处理文件重写配置文件.我循环遍历文件的行并查找我想要用指定的新行替换的行.我有一个’函数’将行写入文件问题是%Text%是一个嵌入双引号的字符串.然后失败了.可能还有其他角色也会导致失败.如何才能使用配置文件中的所有文本?尝试将所有“在文本中替换为^”.^是转义字符,因此“将被视为常规字符你可以尝试以下方法:其他可能导致错误的字符是:

  8. .net – 将控制台应用程序转换为服务?

    我正在寻找不同的优势/劣势,将我们长期使用的控制台应用程序转换为Windows服务.我们为ActiveMQ使用了一个叫做java服务包装器的东西,我相信人们告诉我你可以用它包装任何东西.这并不是说你应该用它包装任何东西;我们遇到了这个问题.控制台应用程序是一个.NET控制台应用程序,默认情况下会将大量信息记录到控制台,尽管这是可配置的.任何推荐?我们应该在VisualStudio中将其重建为服务吗?我使用“-install”/“-uninstall”开关执行此操作.例如,seehere.

  9. windows – 捕获外部程序的STDOUT和STDERR *同时*它正在执行(Ruby)

    哦,我在Windows上:-(实际上,它比我想象的要简单,这看起来很完美:…是的,它适用于Windows!

  10. windows – 当我试图批量打印变量时,为什么我得到“Echo is on”

    我想要执行一个简单的批处理文件脚本:当我在XP中运行时,它给了我预期的输出,但是当我在Vista或Windows7中运行它时,我在尝试打印值时得到“EchoisOn”.以下是程序的输出:摆脱集合表达式中的空格.等号(=)的两侧可以并且应该没有空格BTW:我通常在@echo关闭的情况下启动所有批处理文件,并以@echo结束它们,所以我可以避免将代码与批处理文件的输出混合.它只是使您的批处理文件输出更好,更清洁.

返回
顶部