当前位置: 首页 > Linux

inetsocket和packetsocket

时间:2023-04-06 02:49:05 Linux

大多数调试过网络程序的人都用过tcpdump,那么你知道tcpdump是怎么工作的吗?tcpdump等工具也称为Sniffer,可以在不影响应用程序正常数据包的情况下,将经过网卡的数据包复制到Sniffer中,然后进行处理过滤,最后呈现给用户。本文不分析tcpdump的具体实现,只是通过tcpdump来揭示一些网络编程中大多数人容易忽略的一个话题:Socket参数对用户接收消息的影响。。。相信大家已经在接触过Socket编程就知道你应该知道下面的API#includesockfd=socket(intsocket_family,intsocket_type,intprotocol);是的,它基本上是套接字编程的第一步,创建一个套接字。他有三个参数,但是又有多少人真正理解这些参数的含义呢?对于TCP或UDP应用的开发者,很容易从网上找到(复制)这样的例子:/*CreateTCPsocket*/sockfd=socket(AF_INET,SOCK_STREAM,0);/*CreateUDPsocket*/sockfd=socket(AF_INET,SOCK_DGRAM,0)为什么第一个参数用AF_INET,第二个参数为什么用SOCK_STREAM或SOCK_DGRAM,第三个参数填0?socket_family第一个参数表示创建的socket所属的地址簇或协议簇,取值以AF或PF开头,定义在(include\linux\socket.h)中,实际使用时没有区别(有是两个不同的名称只是因为历史设计原因)。最常用的值有AF_INET、AF_PACKET、AF_UNIX等。AF_UNIX用于宿主机内部的进程间通信,本文不做讨论。AF_INET和AF_PACKET的区别在于,使用前者只能看到IP层以上的东西,而后者只能看到链路层的信息。这意味着什么?为了说明这个问题,我们需要知道网络数据包的分类。如下图所示:EthernetII帧是使用最广泛的帧类型(当然还有PPP等其他链路帧类型)。在EthernetII帧内部,大致可以分为IP包和其他包。我们熟悉的TCP或UDP数据包都属于IP数据包。AF_INET对应IP包,AF_PACKET对应EthernetII包。AF_INET创建的socket称为inetsocket,AF_PACKET创建的socket称为packetsocket_type&protocol第一个参数family会影响第二个参数socket_type和第三个参数protocol的取值范围第二个参数socket_type表示套接字类型。它的值很少,常见的有以下三个enumsock_type{SOCK_STREAM=1,/*stream(connection)socket*/SOCK_DGRAM=2,/*datagram(conn.less)socket*/SOCK_RAW=3,/*原始套接字*/};第三个参数protocol表示socket上消息的协议。对于AF_INET地址簇,protocol取值范围为IP包协议类型如IPPROTO_TCPIPPROTO_UDPIPPROTO_ICMP,或特殊值IPPROTO_IP=0。对于AF_PACKET地址簇,protocol取值范围为以太网帧协议类型如ETH_P_IPETH_P_ARP。inetsocket的协议切换表每个inetsocket只能收发一类IP协议的数据包,在创建socket时就确定了(协议参数),比如TCPsocket不能收发UDP数据包是的,反之反之亦然。而且,protocol的取值也受socket_type的限制。如果值不匹配,套接字创建操作将返回失败。/*错误值,返回失败*/sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_TCP);内核通过协议切换表记录哪些值是有效的,初始化时inet会在协议切换表中注册支持的协议在以socket_type为KEY的链表上:创建socket时,inet_create会匹配socket_type和protocolin协议切换表list_for_each_entry_rcu(answer,&inetsw[sock->type],list){err=0;/*检查非野生匹配。*/if(protocol==answer->protocol){if(protocol!=IPPROTO_IP)中断;}else{/*检查两个异常情况。*/if(IPPROTO_IP==protocol){protocol=answer->protocol;休息;}if(IPPROTO_IP==answer->protocol)中断;}错误=-EPROTONOSUPPORT;}IPPROTO_IP的值为0,当用户使用0作为创建第三个socket时,有两个参数时,会匹配链表上的第一个协议,这就是为什么创建TCP或UDP时第三个参数可以为0的原因socket,0表示由内核自动选择。··/*CreateTCPsocket*/sockfd=socket(AF_INET,SOCK_STREAM,0);/*CreateUDPsocket*/sockfd=socket(AF_INET,SOCK_DGRAM,0)rawinetsocket对于inetsocket,一个TCP报文可以是分解成这样:packet=IPHeader+TCPHeader+Payload如果我们使用SOCK_STREAM创建的TCPsocket,应用程序通过send发送数据时只需要提供Payload,IPHeader和TCPHeader由内核Assembly决定做完了。在接收方向,应用程序只能通过recv接收payload,RAWsocket为应用程序提供底层控制能力ints=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);使用上面的接口可以创建一个更原始的TCPsocket,当我们使用这个socket发送数据时,我们需要提供Payload和TCPHeader,而IPHeader仍然是由内核协议栈自动组装的。如果要手动组装IPHeader,有两种方法:第一种是使用协议IPPROTO_RAWints=socket(AF_INET,SOCK_RAW,IPPROTO_RAW);二是设置IP_HDRINCL的socket选项。ints=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);整数一=1;constint*val=&one;if(setsockopt(s,IPPROTO_IP,IP_HDRINCL,val,sizeof(one))<0){printf("ErrorsettingIP_HDRINCL.Errornumber:%d.Errormessage:%s\n",errno,strerror(errno));exit(0);}以上两个方法都是告诉内核,IPHeader也是由应用程序自己提供。数据包套接字的控制范围是IP数据包,而数据包套接字的控制范围扩展到以太网层数据包。对于inetsocket,第二个参数socket_type只能选择SOCK_DGRAM、SOCK_RAW或SOCK_PACKET,protocol表示支持的网络层协议类型。ProtocolHandler对于以太网帧,不同的网络层协议类型(如IPARPPPPoE)有不同的接收和处理功能。在内核中,这是协议处理程序。内核中的ProtoclHandler是这样组织的注意:注意这个patch在dev下的ProtoclHandler中增加了ptype_all链表和ptype_base链表。不管网卡是否使用NAPI,内核最终都会调用__netfi_receive_skb来接收报文。该函数会遍历ptype_all链表Registeredhandler,然后遍历ptype_base具体协议链上所有注册的handler。handler的注册是通过dev_add_pack完成的。如果没有指定protocol(ETH_P_ALL),handler会注册到ptype_all上(这里默认会注册tcpdump),否则会根据protocol注册到ptype_base的一个链表上。在消息接收过程中,同一个skb会被deliver_skb发送给多个handler(至少要经过ptype_all链表上的handler)。内核启动时,inet会注册一个handler,它支持IP协议。所有的AF_INETsocket实际上都共享这样一个handler,对应的接收函数是ip_rcv。区分消息是哪个socket是后面的工作。/*net/ipv4/af_inet.c*/staticstructpacket_typeip_packet_type__read_mostly={.type=cpu_to_be16(ETH_P_IP),.func=ip_rcv,};staticint__initinet_init(void){//省略代码dev_add_pack(&ip_packet_type);//代码省略}对于AF_PACKET,handler是在packet_create中单独注册的,即每个AF_PACKETsocket都有一个独立的handlerstaticintpacket_create(structnet*net,structsocket*sock,intprotocol,intkern){//代码省略po->prot_hook.func=packet_rcv;//代码省略register_prot_hook(sk);//这里去dev_add_pack}单独的handler,这样在接收到packet_rcv这个函数的时候,就已经可以知道是属于哪个socket的数据了。rawpacketsocket对于AF_PACKET,一个报文可以这样分解:packet=EthernetHeader+Payload而SOCK_DGRAM和SOCK_RAW的区别在于在接收方向上,应用程序使用SOCK_DGRAMsocket收到的报文已经去掉了EthernetHeader,而SOCK_RAW套接字将被保留。Packetsocket和tcpdump回到本文最初的问题,tcpdump是如何完成嗅探工作的?这是正确的!正是使用的packetsocket:tcpdump作为sniffer,不能影响正常的packet收发,所以需要一个单独的protocolhandler,这样kernel收到的packet会被复制交给tcpdumptcpdump不仅可以抓取IP包,还可以抓取链路层信息或者其他一些非IP包。REF-pf-inet-sockets-and-pf-packetdata-link-access-and-zero-copyraw-socket-in-linuxraw-sockets-c-code-linux之间的区别

最新推荐
猜你喜欢