原始套接字

    最近自己在学习套接字,感觉原始套接字这块内容比较难以理解,但这部分的内容却十分有意思,所以自己对自己的学习到的知识点进行总结。

    在学习int socket(int domain, int type, int protocol)时发现在函数的第二个参数位置type类型有3种。

SOCK_STREAM   字节流套接字   

SOCK_DGRAM     数据报套接字

SOCK_RAM           原始套接字

字节流套接字提供有序,可靠,双向字节流的连接。

数据报套接字是在AF_INET域中通过UDP/IP连接实现,它提供的是一种无需连接的不可靠服务。

原始套接字与前面两种套接字不同。详细原因看后面的总结。

1.套接字的域

     域指定套接字通信中使用的网络介质。最常见的套接字域是AF_INET,它是指Internet网络,许多Linux局域网使用的都是该网络,当然,因特网自身用的也是它。其底层的协议——网际协议(IP)只有一个地址族,它使用一种特定的方式来指定网络中的计算机,即IP地址。

2.原始套接字

     如下图中所示:在网际网协议族中的传输层中,有一个通道,使得网络应用可以绕过传输层直接使用ip层。原始套接字就是这样工作的。因此导致了原始套接字的独有的特性。

那么到底原始套接字与一般的套接字有什么不同呢?

总的来说就是以前由系统做的事情,现在由我们做。当我们创建一般套接字时,我们只是把我们发送的内容(buffer)传递给了系统,系统收到我们的数据后,会自动的调用相应的模块给数据加上TCP报头和IP报头,再发送出去。而当创建原始套接字时,就需要我们自己创建TCP,IP报文头部,系统仅仅是帮我们把数据发送出去。这样看来原始套接字只能接收ICMP, IGMP等数据报,而要传送TCP,UDP这样的数据报,需要我们自己创建报头。

    

协议栈的原始套接字可以分为两类:

(1)链路层原始套接字

用来接收和发送链路层的MAC帧,再发送时需要我们自己来构造和封装MAC首部。

构造链路层原始套接字

socket(AF_PACKET, SOCK_RAW, protocol);

其中protocol指定使用的协议。在/usr/include/linux/if_ether.h中对不同的协议有不同的声明。

#define ETH_P_IP	0x0800		/* Internet Protocol packet	*/
#define ETH_P_ARP	0x0806		/* Address Resolution packet	*/
#define ETH_P_RARP      0x8035		/* Reverse Addr Res packet	*/
#define ETH_P_ALL	0x0003		/* Every packet (be careful!!!) */

(2)网络层原始套接字

构造网络层原始套接字

socket(AF_INET, SOCK_RAW, protocol);

其中protocol指定使用的协议。在/usr/include/netinet/in.h中有声明。

enum
  {
    IPPROTO_IP = 0,	   /* Dummy protocol for TCP.  */
#define IPPROTO_IP		IPPROTO_IP
    IPPROTO_HOPOPTS = 0,   /* IPv6 Hop-by-Hop options.  */
#define IPPROTO_HOPOPTS		IPPROTO_HOPOPTS
    IPPROTO_ICMP = 1,	   /* Internet Control Message Protocol.  */
#define IPPROTO_ICMP		IPPROTO_ICMP
    IPPROTO_IGMP = 2,	   /* Internet Group Management Protocol. */
#define IPPROTO_IGMP		IPPROTO_IGMP
    IPPROTO_IPIP = 4,	   /* IPIP tunnels (older KA9Q tunnels use 94).  */
#define IPPROTO_IPIP		IPPROTO_IPIP
    IPPROTO_TCP = 6,	   /* Transmission Control Protocol.  */
#define IPPROTO_TCP		IPPROTO_TCP
    IPPROTO_EGP = 8,	   /* Exterior Gateway Protocol.  */
#define IPPROTO_EGP		IPPROTO_EGP
    IPPROTO_PUP = 12,	   /* PUP protocol.  */
#define IPPROTO_PUP		IPPROTO_PUP
    IPPROTO_UDP = 17,	   /* User Datagram Protocol.  */

TCP报头的结构体

原始套接字_第1张图片

tcp报头占20个字节

struct tcphdr
  {
    u_int16_t source;  //16位源端口
    u_int16_t dest;    //16位目的端口
    u_int32_t seq;     //32位序列号
    u_int32_t ack_seq; //32位确认号
#  if __BYTE_ORDER == __LITTLE_ENDIAN    //小端模式   通常PC电脑都是小端模式
    u_int16_t res1:4;    //4位TCP偏移量
    u_int16_t doff:4;    //4位保留位 
    u_int16_t fin:1;     //6个标志位
    u_int16_t syn:1;
    u_int16_t rst:1;
    u_int16_t psh:1;
    u_int16_t ack:1;
    u_int16_t urg:1;
    u_int16_t res2:2;    //2位保留位
#  elif __BYTE_ORDER == __BIG_ENDIAN    //大端模式
    u_int16_t doff:4;
    u_int16_t res1:4;
    u_int16_t res2:2;
    u_int16_t urg:1;
    u_int16_t ack:1;
    u_int16_t psh:1;
    u_int16_t rst:1;
    u_int16_t syn:1;
    u_int16_t fin:1;
#  else
#   error "Adjust your  defines"
#  endif
    u_int16_t window;    //窗口大小
    u_int16_t check;     //校验和
    u_int16_t urg_ptr;   //紧急位
};
# endif /* __FAVOR_BSD */
这里在构造TCP报头的时候,原始套接字提供了个有用的参数IP_HDRINCL,

setsocketopt(int socketfd, int level, int optname, const void *optval, socklen_t optlen);

通过setsocketopt()函数可以设置套接字的属性。当把第三个参数置为IP_HDRINCL时,我们可以从IP报文首部第一个字节开始依次构造整个IP报文的所有的选项,但是IP报文头的标识字段,和IP首部校验和由内核自己维护,不需要我们关心。如果不设置该选项,我们所构造的报文是从IP首部之后的第一个字节开始,IP首部由内核维护,首部协议字段设置成调用socket()函数时我们传递的第三个参数。

原始套接字的作用:

许多网络的诊断工具都是利用原始套接字来实现的,例如tcpdump抓包工具,ping检查网络连接工具,traceroute跟踪IP报文在网络中的路由经过。

你可能感兴趣的:(Linux,网络编程)