Linux下原始套接字编程常用的头文件

socket协议族

socket常用的协议有PF_INETPF_PACKET,是AF_INET协议族下的两类,具体可以参考socket协议族

  • PF_INET表示Internet上使用的是TCP/IP协议,提供SOCK_STREAM、SOCK_DGRAM、SOCK_RAW三种服务,是常用的套接字协议。
  • PF_PACKET类型的socket是一类允许应用程序不经过网络协议栈而直接读写网络设备的接口,应用程序必须自己提供包括第 2 层报头在内的各级报文头部。这种类型常用以实现嗅探器 ,为了获得各种协议类型的报文 ,常配合ETH_P_ALL 类型来匹配第 2 层收到的各类报文。PF_PACKET 支持SOCK_DGRAM、SOCK_RAW两种服务类型。前者由内核添加或者删除Ethernet 帧头部,后者可以由应用程序对 Ethernet 帧头部填充。PF_PACKET常用作构造原始套接字。

在Linux系统中由 libpcap 库实现了对PF_PACKET 套接字的封装,提供一组更高级、更简单易用的接口。

INET协议族的原始套接字和PACKET协议族区别体现在创建和接收报头的能力不同:

  • INET 协议族的原始套接字可以创建第 4 层及其以上各层协议的报头,配合 IP_HDRINCL 选项也可以创建IP 报头,但是网络层必须使用 IP 协议
  • PF_PACKET 协议族要求用户提供第 2 层及其以上的各级报头,因此适用于其他各种网络应协议。
  • 在接收时,INET 协议族的原始套接字只能获得 IP 报文的负载部分,而无法获得 IP 报头; PF_PACKET 协议族不但可以获得 IP 报头,还可以获得 Ethernet 帧头部的内容。

socket 地址结构体

socket地址结构体用于创建套接字时标识一个套接字

struct in_addr
  {
    in_addr_t s_addr;    
  };  //表示ip地址的结构体
struct sockaddr{
    unsigned short sa_farnily;
    char sa_data[14];
};    //socket通用的地址结构体,一般只在创建套接字时使用
struct sockaddr_in{
	unsigned short      sin_family;  //一般为AF_INET
	unsigned short int  sin_port;    //需要监听的端口号
	struct in_addr      sin_addr;    //设置为INADDR_ANY,表示可以和任何主机通信
	unsigned char       sin_zero[8]  //是用来填充的
};      //AF_INET专用的地址结构体

详细情况可以参考 socket地址结构体

TCP报文

TCP 连接的两端使用两对IP地址和端口识别这个连接,并且向监听这个端口的应用程序发送数据 。TCP 报头的固定长度是 20 个字节, 如果使用一些选项,最大长度为 60 个字节。TCP 报头格式为:
在这里插入图片描述
各字段依次为:

  1. 源端口:16 位,本地 TCP 端口号。
  2. 目的地端口:16 位,目的 TCP 端口号。
  3. 序号:32 位,用来跟踪发送报文字节顺序的序号。
  4. 确认编号:32 位,表示已经正确接收字节的序号 。
  5. 报头长度:4 位,表示以 4 个字节为单位的报头长度 。 如果不使用选项,报头长度值为 5。
  6. 保留:6 位,留做以后使用。
  7. 标记flags:6 位,每一位标记都有具体的含义。
  1. URG:紧急字段指针。如果为1,此时 TCP 协议报头结构中的紧急指针有效,表示数据包中包含紧急数据 。
  2. ACK:确认标志位。如果为 1 ,表示包中的确认号有效 。
  3. PSH:推送功能 。 如果为 1 ,表示接收端应尽快将数据传送给应用层 。
  4. RST:重置位,用来复位一个连接 。 RST 标志置位为1的数据包称为复位包 。 一般如果 TCP 收到的一个分段明显不属于该主机上的任何一个连接,则向远端发送一个复位包。
  5. SYN:用来建立连接,让连接双方同步的序列号。如果 SYN=1且 ACK =0 ,表示 数据包为连接请求,如果 SYN=1且ACK=1,则表示接受连接。
  6. FIN:表示发送端已经没有数据要求传输了,请求释放连接。
  1. 窗口尺寸:16 位,表示要求对方必须维持的窗口字节数。
  2. 校验和:16 位, TCP 报头和数据的校验和 。
  3. 紧急指 针:16 位,指向跟在 URG 数据后面的数据的序列号的偏移值
  4. 选项:MSS 、窗口比例等。

tcphdr

#include <netinet/tcp.h>           // tcp头部
struct tcphdr {
    __be16 source;
    __be16 dest;
    __be32 seq;
    __be32 ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u16   res1:4,
            doff:4,
            fin:1,
            syn:1,
            rst:1,
            psh:1,
            ack:1,
            urg:1,
            ece:1,
            cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
    __u16   doff:4,
            res1:4,
            cwr:1,
            ece:1,
            urg:1,
            ack:1,
            psh:1,
            rst:1,
            syn:1,
            fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
    __be16 window;
    __be16 check;
    __be16 urg_ptr;
};

UDP报文

#include <netinet/udp.h>           // udp头部
/* UDP header as specified by RFC 768, August 1980. */
struct udphdr
{
  __extension__ union
  {
    struct
    {
      u_int16_t uh_sport;		/* source port */
      u_int16_t uh_dport;		/* destination port */
      u_int16_t uh_ulen;		/* udp length */
      u_int16_t uh_sum;		    /* udp checksum */
    };
    struct
    {
      u_int16_t source;
      u_int16_t dest;
      u_int16_t len;
      u_int16_t check;
    };
  };
};

IP数据报

#include <netinet/ip.h>           // ip头部
struct iphdr
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ihl:4;
    unsigned int version:4;
#elif __BYTE_ORDER == __BIG_ENDIAN
    unsigned int version:4;
    unsigned int ihl:4;
#else
# error	"Please fix <bits/endian.h>"
#endif
    u_int8_t tos;             //服务类型
    u_int16_t tot_len;        //总长度
    u_int16_t id;             //标识
    u_int16_t frag_off;       //标志+片偏移
    u_int8_t ttl;             //生存时间
    u_int8_t protocol;        //协议
    u_int16_t check;          //头校验和
    u_int32_t saddr;          //源IP地址
    u_int32_t daddr;          //目的IP地址
    /*The options start here. */
  };

IPv6报文

IPv6的实现代码在头文件include/net/ipv6.h中,具体的几个函数有:

  • 在数据链路层发现网络层使用IPv6 协议时调用函数 ipv6_ rcv 来接收并继续处理该报文
  • 当上层协议(如TCP 或 UDP)发送IPv6报文时,调用ip6_ xmit发送函数
  • 通过IPv6发送 ICMP 报文时调用 icmpv6_send函数
  • IPv6转发报文的工作由 ip6_forward函数完成
    另外在 net/ipv6/ip6_fib. c 中实现了针对 IPv6地址的路向表管理,在 net/ipv6/tcp_ipv6. c中实现了在IPv6 条件下 TCP协议必须进行的修改 。

IGMP报文

IGMP协议用以管理组播通信的成员,在头文件net/ipv4/igmp.c中实现。IGMP报文封装在IP报文中,由IP报文头部的服务类型指定,调用igmp_rcv函数处理。

ARP报文

ARP 协议的实现代码保存在include/net/if_arp.hnet/core/ipv4/arp.c 中,由arp_sendarp_rcv函数发送和接收ARP报文。
在这里插入图片描述
  在这里插入图片描述
  硬件类型,1代表是以太网。

协议类型,表明上层协议的类型,这里是0x0800,表示上层协议是IP协议

硬件地址长度,毫无疑问是6个字节

协议长度,这里IP协议的长度是4个字节

操作类型,在报文中占2个字节,1表示ARP请求,2表示ARP应答,3表示RARP请求,4表示RARP应答

ICMP报文

#include <netinet/ip_icmp.h>       //icmp头

struct icmphdr
{
};

struct icmp
{
  u_int8_t  icmp_type;	/* type of message, see below */
  u_int8_t  icmp_code;	/* type sub code */
  u_int16_t icmp_cksum;	/* ones complement checksum of struct */
  union
  {
    u_char ih_pptr;		/* ICMP_PARAMPROB */
    struct in_addr ih_gwaddr;	/* gateway address */
    struct ih_idseq		/* echo datagram */
    {
      u_int16_t icd_id;
      u_int16_t icd_seq;
    } ih_idseq;
    u_int32_t ih_void;

    /* ICMP_UNREACH_NEEDFRAG -- Path MTU Discovery (RFC1191) */
    struct ih_pmtu
    {
      u_int16_t ipm_void;
      u_int16_t ipm_nextmtu;
    } ih_pmtu;

    struct ih_rtradv
    {
      u_int8_t irt_num_addrs;
      u_int8_t irt_wpa;
      u_int16_t irt_lifetime;
    } ih_rtradv;
  } icmp_hun;

常用函数

对套接字操作的常用函数有以下几个,都包含在以下头文件中:

#include <sys/types.h>
#include <sys/socket.h>

发送函数

  1. write :调用 connect 函数建立连接之后,可 以使用 write 函数来发送数据。但 write 函数使用的是文件的写功能,无法支持 socket 通信时所特有的功能,如发送带外数据等。 - 建立连接后,将buf 中 nbytes 字节的内容写入文件描述符:
ssize_t write (int sockfd, const void * buf, size_t nbytes);
  1. sendmsg:可以一 次发送多个数据缓冲区的内容,但要求用户自行创建所需的 msghdr 结构。msghdr 是一种可以实现批量数据传递的数据结构。
ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flags);
  1. send:send 函数与 write 函数十分相似,在保留 write 函数的三个参数 的同时,增加 了一个flags 参数用来实现一些socket所特有的功能,例如设定MSG__OOB 标志来发送带外数据 。
ssize_t send(int sockfd, const void*buf, size_t len, int flags) ;` //flags一般为0,同write
  1. sendto:sendto 函数来发送非连接的 socket 数据,提供了设定报文目的地址和目的端口的能力。当用于面向连接的通
    信时(套接字类型为SOCK_STREAM),将省略参数 totolen
ssize_t sendto(int sockfd, const void*buf, size_t len, int flags, const struct sockaddr* to, socklen_t tolen)

一般情况下, buf 指针所指向的数据包不包含IP头,只包含IP报文的负载部分。如果要处理IP报头,则需在之前调用setsocketopt 函数设置套接字的选项,此时buf将包含IP头部。代码如下:

int flag= 1;
setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &flag, sizeof(int));

接收函数

  1. read 从文件描述符读取内容到buf:
ssize_t read(int sockfd, void* buf , size_t nbyte);
  1. recvmsg
ssize_t recvmsg(int sockfd, struct msghdr* msg , int flags);
  1. recv
ssize t recv(int sockfd, const void*buf, size_t len, int flags);
  1. recvfrom 性质同sendto
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* from, socklen_t* fromlen);
//flags用于控制是否接收带外数据

一般在接收数据时,recvfrom 函数处于阻塞状态,直到收到一个数据包才会返回 。如果希望 recvfrom 在接收数据包时处于非阻塞状态,则需要调用函数 fentl 将套接字设置为非阻塞筷式。具体代码如下:

fcntl(int sockfd, F_SETFL, O_NONBLOCK);

其他函数

  • 创建套接字:
int socket (int domain, int type, int protocol);`  // protocol:IPPROTO_IP 、IPPROTO_TCP 、IPPROTO_UDP 、 IPPROTO_ICMP
  • 绑定端口:
int bind (int sockfd, const struct sockaddr* my_addr, socklen_t addrl en) ;
  • 监听请求:
int listen (int sockfd, int backlog);`  //backlog:请求队列的最大长度。
  • 服务器端监听到请求后建立连接:
int accept (int sockfd, struct sockaddr* addr, socklen_t *addrlen);`//addr: 客户端结构体sockaddr的指针 。
  • 客户端向服务器端发起连接请求:
int connect(int sockfd, const struct sockaddr * serv_addr , socklen_t addrlen);`//服务部端的连接信息
  • 设置当前套接字的选项:
int setsockopt(int sockfd, //
			   int level, //表示控制套接字的层次,SOL_SOCKET(通用套接字选项),IPPROTO_IP和IPPROTO_TCP
			   int optname, //需要设置选项的名称,如果需要构建数据包的IP头就将Optname设为IP_HDRINCL
			   const char vo1d* optval, //指向存放选项值的缓冲区。optval需要根据选项不同的数据类型进行转换; 
			   socklen_t * optlen  //表示optval所指向的缓冲区的长度。);

linux下socket编写常用头文件:

<sys/socket.h>     //与套接字相关的函数声明和结构体定义,如socket()、bind()、connect()及struct sockaddr的定义等
<sys/types.h>       //包含很多类型重定义,如pid_t、int8_t等
<arpa/inet.h>        //inet_addr,inet_ntop()、inet_ntoa()等
<netdb.h>             //某些结构体定义、宏定义和函数声明,如struct hostent、struct servent、gethostbyname()、gethostbyaddr()、herror()等. 文件如其名,包括结构hostent(主机环境),获得主机的信息的几个函数(gethostbyname)。似乎这个就是定义主机的各项环境,例如hostname等等
<net/ethernet.h>  // 包括几个以太网的数据结构,ether_addr(mac帧结构),ether_header(以太帧的头部)

<netinet/in.h>              // sockaddr_in, "man 7 ip" ,htons
<netinet/in.h>              //某些结构体声明、宏定义,如sockaddr_in、PROTO_ICMP、INADDR_ANY, 网络字节转换(ntoh,hton)
<netinet/ip.h>             // 这个头文件和linux/ip.h似乎很相似,也有iphdr的数据结构,同时还包括了bsd中的ipheader结构定义。
<netinet/if_ether.h>    //ether_arp的数据结构
<netinet/ether.h>       // 以太祯的网络字节和ascii字节的转换,包括ether_ntoa(),ether_aton这样的函数定义

<linux/ip.h>              //iphdr的数据结构,以及一些ip层的数据定义,同理的还有tcp.h,udp.h等等
<linux/if.h>               //主要的socket头文件,似乎修改自unix的if.h,定义了网卡的接口信息的宏,例如IFF_UP.另外有数个重要的interface的数据结构定义,包括ifreq,ifconf,ifmap
<linux/if_packet.h>   // 原始数据包的数据结构定义,包括sockaddr_pkt,sockaddr_ll,想接收原始数据包的不能错过这个文件。同理的还有if_ppp.h,if_tun.h等等

<unistd.h>              //read,write
<poll.h>                  //poll,pollfd
<error.h>               //perror
<stdio.h>               //C语言标准输入输出库
<errno.h>              //errno
<stdlib.h>              //标准库
<string.h>             // memset
<string>
<iostream>
<stdlib.h>              //标准库
<sys/ioctl.h>         //I/O控制操作相关的函数声明,如ioctl()

具体参见linux下socket编程常用头文件
下面为部分内容:

sys/types.h:数据类型定义

sys/socket.h:提供socket函数及数据结构

netinet/in.h:定义数据结构sockaddr_in

arpa/inet.h:提供IP地址转换函数

netdb.h:提供设置及获取域名的函数

sys/ioctl.h:提供对I/O控制的函数

sys/poll.h:提供socket等待测试机制的函数


其他在网络程序中常见的头文件 
unistd.h:提供通用的文件、目录、程序及进程操作的函数

errno.h:提供错误号errno的定义,用于错误处理

fcntl.h:提供对文件控制的函数

time.h:提供有关时间的函数

crypt.h:提供使用DES加密算法的加密函数

pwd.h:提供对/etc/passwd文件访问的函数

shadow.h:提供对/etc/shadow文件访问的函数

pthread.h:提供多线程操作的函数

signal.h:提供对信号操作的函数

sys/wait.h、sys/ipc.h、sys/shm.h:提供进程等待、进程间通讯(IPC)及共享内存的函数

建议: 在编写网络程序时,可以直接使用下面这段头文件代码

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <stdarg.h>
#include <fcntl.h>
#include <fcntl.h>

涉及到用户权限及密码验证问题时加入如下语句:

#include <shadow.h>
#include <crypt.h>
#include <pwd.h> 

需要注意的是,应该在编译时链接加密算法库,即增加编译选项:
-lcrypt

涉及到文件及时间操作加入如下语句:

#include <sys/time.h>
#include <utime.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/file.h>

涉及到多进程操作时加入如下语句:

#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>

涉及到多线程操作时加入如下语句:

#include <pthread.h>
#include <sys/poll.h>

需要注意的是,应该在编译时链接线程库,即增加编译选项:-lthread

猜你喜欢

转载自blog.csdn.net/qq_34415266/article/details/83904857