一、struct ethhdr 结构体

1、使用struct ethhdr结构体来表示以太网帧的头部。这个struct ethhdr结构体位于#include<linux/if_ether.h>之中。

#define ETH_ALEN 6         //定义了以太网接口的MAC地址的长度为6个字节
#define ETH_HLAN 14       //定义了以太网帧的头长度为14个字节
#define ETH_ZLEN 60       //定义了以太网帧的最小长度为 ETH_ZLEN + ETH_FCS_LEN = 64个字节
#define ETH_DATA_LEN 1500    //定义了以太网帧的最大负载为1500个字节
#define ETH_FRAME_LEN 1514   //定义了以太网正的最大长度为ETH_DATA_LEN + ETH_FCS_LEN = 1518个字节
#define ETH_FCS_LEN 4       //定义了以太网帧的CRC值占4个字节
struct ethhdr
{
  unsigned char h_dest[ETH_ALEN]; //目的MAC地址
  unsigned char h_source[ETH_ALEN]; //源MAC地址
  __u16 h_proto ; //网络层所使用的协议类型
}__attribute__((packed)) //用于告诉编译器不要对这个结构体中的缝隙部分进行填充操作;

2、网络层所使用的协议类型有(常见的类型):其他协议请查看linux/if_ether.h

#define ETH_P_IP 0x0800 //IP协议

#define ETH_P_ARP 0x0806 //地址解析协议(Address Resolution Protocol)

#define ETH_P_RARP 0x8035 //返向地址解析协议(Reverse Address Resolution Protocol)

#define ETH_P_IPV6 0x86DD //IPV6协议

 1 /*
 2  *      These are the defined Ethernet Protocol ID's.
 3  */
 4 
 5 #define ETH_P_LOOP      0x0060          /* Ethernet Loopback packet     */
 6 #define ETH_P_PUP       0x0200          /* Xerox PUP packet             */
 7 #define ETH_P_PUPAT     0x0201          /* Xerox PUP Addr Trans packet  */
 8 #define ETH_P_TSN       0x22F0          /* TSN (IEEE 1722) packet       */
 9 #define ETH_P_IP        0x0800          /* Internet Protocol packet     */
10 #define ETH_P_X25       0x0805          /* CCITT X.25                   */
11 #define ETH_P_ARP       0x0806          /* Address Resolution packet    */
12 #define ETH_P_BPQ       0x08FF          /* G8BPQ AX.25 Ethernet Packet  [ NOT AN OFFICIALLY REGISTERED ID ] */
13 #define ETH_P_IEEEPUP   0x0a00          /* Xerox IEEE802.3 PUP packet */
14 #define ETH_P_IEEEPUPAT 0x0a01          /* Xerox IEEE802.3 PUP Addr Trans packet */
15 #define ETH_P_BATMAN    0x4305          /* B.A.T.M.A.N.-Advanced packet [ NOT AN OFFICIALLY REGISTERED ID ] */
16 #define ETH_P_DEC       0x6000          /* DEC Assigned proto           */
17 #define ETH_P_DNA_DL    0x6001          /* DEC DNA Dump/Load            */
18 #define ETH_P_DNA_RC    0x6002          /* DEC DNA Remote Console       */
19 #define ETH_P_DNA_RT    0x6003          /* DEC DNA Routing              */
20 #define ETH_P_LAT       0x6004          /* DEC LAT                      */
21 #define ETH_P_DIAG      0x6005          /* DEC Diagnostics              */
22 #define ETH_P_CUST      0x6006          /* DEC Customer use             */
23 #define ETH_P_SCA       0x6007          /* DEC Systems Comms Arch       */
24 #define ETH_P_TEB       0x6558          /* Trans Ether Bridging         */
25 #define ETH_P_RARP      0x8035          /* Reverse Addr Res packet      */
26 #define ETH_P_ATALK     0x809B          /* Appletalk DDP                */
27 #define ETH_P_AARP      0x80F3          /* Appletalk AARP               */
28 #define ETH_P_8021Q     0x8100          /* 802.1Q VLAN Extended Header  */
29 #define ETH_P_IPX       0x8137          /* IPX over DIX                 */
30 #define ETH_P_IPV6      0x86DD          /* IPv6 over bluebook           */
31 #define ETH_P_PAUSE     0x8808          /* IEEE Pause frames. See 802.3 31B */
32 #define ETH_P_SLOW      0x8809          /* Slow Protocol. See 802.3ad 43B */
33 #define ETH_P_WCCP      0x883E          /* Web-cache coordination protocol
34                                          * defined in draft-wilson-wrec-wccp-v2-00.txt */
35 #define ETH_P_MPLS_UC   0x8847          /* MPLS Unicast traffic         */
36 #define ETH_P_MPLS_MC   0x8848          /* MPLS Multicast traffic       */
37 #define ETH_P_ATMMPOA   0x884c          /* MultiProtocol Over ATM       */
38 #define ETH_P_PPP_DISC  0x8863          /* PPPoE discovery messages     */
39 #define ETH_P_PPP_SES   0x8864          /* PPPoE session messages       */
40 #define ETH_P_LINK_CTL  0x886c          /* HPNA, wlan link local tunnel */
41 #define ETH_P_ATMFATE   0x8884          /* Frame-based ATM Transport
42                                          * over Ethernet
43                                          */
44 #define ETH_P_PAE       0x888E          /* Port Access Entity (IEEE 802.1X) */
45 #define ETH_P_AOE       0x88A2          /* ATA over Ethernet            */
46 #define ETH_P_8021AD    0x88A8          /* 802.1ad Service VLAN         */
47 #define ETH_P_802_EX1   0x88B5          /* 802.1 Local Experimental 1.  */
48 #define ETH_P_TIPC      0x88CA          /* TIPC                         */
49 #define ETH_P_MACSEC    0x88E5          /* 802.1ae MACsec */
50 #define ETH_P_8021AH    0x88E7          /* 802.1ah Backbone Service Tag */
51 #define ETH_P_MVRP      0x88F5          /* 802.1Q MVRP                  */
52 #define ETH_P_1588      0x88F7          /* IEEE 1588 Timesync */
53 #define ETH_P_PRP       0x88FB          /* IEC 62439-3 PRP/HSRv0        */
54 #define ETH_P_FCOE      0x8906          /* Fibre Channel over Ethernet  */
55 #define ETH_P_TDLS      0x890D          /* TDLS */
56 #define ETH_P_FIP       0x8914          /* FCoE Initialization Protocol */
57 #define ETH_P_80221     0x8917          /* IEEE 802.21 Media Independent Handover Protocol */
58 #define ETH_P_LOOPBACK  0x9000          /* Ethernet loopback packet, per IEEE 802.3 */
59 #define ETH_P_QINQ1     0x9100          /* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
60 #define ETH_P_QINQ2     0x9200          /* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
61 #define ETH_P_QINQ3     0x9300          /* deprecated QinQ VLAN [ NOT AN OFFICIALLY REGISTERED ID ] */
62 #define ETH_P_EDSA      0xDADA          /* Ethertype DSA [ NOT AN OFFICIALLY REGISTERED ID ] */
63 #define ETH_P_AF_IUCV   0xFBFB          /* IBM af_iucv [ NOT AN OFFICIALLY REGISTERED ID ] */
64 
65 #define ETH_P_802_3_MIN 0x0600          /* If the value in the ethernet type is less than this value
66                                          * then the frame is Ethernet II. Else it is 802.3 */
67 
68 /*
69  *      Non DIX types. Won't clash for 1500 types.
70  */
71 
72 #define ETH_P_802_3     0x0001          /* Dummy type for 802.3 frames  */
73 #define ETH_P_AX25      0x0002          /* Dummy protocol id for AX.25  */
74 #define ETH_P_ALL       0x0003          /* Every packet (be careful!!!) */
75 #define ETH_P_802_2     0x0004          /* 802.2 frames                 */
76 #define ETH_P_SNAP      0x0005          /* Internal only                */
77 #define ETH_P_DDCMP     0x0006          /* DEC DDCMP: Internal only     */
78 #define ETH_P_WAN_PPP   0x0007          /* Dummy type for WAN PPP frames*/
79 #define ETH_P_PPP_MP    0x0008          /* Dummy type for PPP MP frames */
80 #define ETH_P_LOCALTALK 0x0009          /* Localtalk pseudo type        */
81 #define ETH_P_CAN       0x000C          /* CAN: Controller Area Network */
82 #define ETH_P_CANFD     0x000D          /* CANFD: CAN flexible data rate*/
83 #define ETH_P_PPPTALK   0x0010          /* Dummy type for Atalk over PPP*/
84 #define ETH_P_TR_802_2  0x0011          /* 802.2 frames                 */
85 #define ETH_P_MOBITEX   0x0015          /* Mobitex (kaz@cafe.net)       */
86 #define ETH_P_CONTROL   0x0016          /* Card specific control frames */
87 #define ETH_P_IRDA      0x0017          /* Linux-IrDA                   */
88 #define ETH_P_ECONET    0x0018          /* Acorn Econet                 */
89 #define ETH_P_HDLC      0x0019          /* HDLC frames                  */
90 #define ETH_P_ARCNET    0x001A          /* 1A for ArcNet :-)            */
91 #define ETH_P_DSA       0x001B          /* Distributed Switch Arch.     */
92 #define ETH_P_TRAILER   0x001C          /* Trailer switch tagging       */
93 #define ETH_P_PHONET    0x00F5          /* Nokia Phonet frames          */
94 #define ETH_P_IEEE802154 0x00F6         /* IEEE802.15.4 frame           */
95 #define ETH_P_CAIF      0x00F7          /* ST-Ericsson CAIF protocol    */
96 #define ETH_P_XDSA      0x00F8          /* Multiplexed DSA protocol     */

 

3、从struct sk_buff *skb 获取 struct ethhdr *eth

static inline struct ethhdr *eth_hdr(const struct sk_buff *skb)

{

     return (struct ethhdr *)skb_mac_header(skb);

}

struct ethhdr *eth = eth_hdr(skb);

//MAC地址的输出格式。 "%02x"所表示的意思是:以16进制的形式输出,每一个16进制字符占一个字节
#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x"

#define MAC_BUF_LEN 18   //定义了用于存放MAC字符的缓存的大小

4、创建一个以太网头结构体struct ethhdr:

int eth_header(struct sk_buff *skb, struct net_device *dev,u16 type, void *daddr, void *saddr, unsigned len)

EXPORT_SYMBOL(eth_header);

skb : 将要去修改的struct sk_buff;

dev : 原网络设备

type: 网络层的协议类型

daddr:目的MAC地址

saddr:源MAC地址

len :一般可为0

 1 int  eth_header( struct  sk_buff *skb,  struct  net_device *dev, u16 type,  void  *daddr,  void  *saddr,  int  len)
 2 {
 3      //将skb->data = skb->data + ETH_ALEN;
 4      struct  ethhdr *eth = ( struct  ethhdr*)skb_push(skb, ETH_ALEN);
 5      
 6      if (type != ETH_P_802_3)
 7         eth->proto = htons(type);  // htons()将本地类型转换为网络类型
 8       else
 9         eth->proto = htons(len);
10      
11      //如果 saddr = NULL的话,以太网帧头中的源MAC地址为dev的MAC地址   
12      if (!saddr)
13         saddr = dev->dev_addr;
14      memcpy (eth->saddr, saddr, ETH_ALEN);
15      
16      if (daddr)
17      {
18         memcpy (eth->daddr, daddr, ETH_ALEN);
19         return  ETH_HLEN ;  //返回值为14
20      }
21      
22      return  -ETH_HLEN;
23 }

5、判断一个网络设备正在接受的struct sk_buff中的网络层所使用的协议类型:

__be16 eth_type_trans(struct sk_buff *skb,struct net_device *dev);

EXPORT_SYMBOL(eth_type_trans);

skb : 为正在接收的数据包;

dev : 为正在使用的网络设备;

返回值:为网络字节序列,所以要使用ntohs()进行转换;

 1 __be16 eth_type_trans( struct  sk_buff *skb,  struct  net_device *dev)
 2 {
 3      struct  ethhdr *eth;
 4      
 5      skb->dev = dev;
 6      eth = eth_hdr(skb);
 7      
 8      if (netdev_uses_dsa_tags(dev))
 9           return  htons(ETH_P_DSA);
10           
11      if (netdev_uses_trailer_tags(dev))
12           return  htons(ETH_P_TRAILER);
13           
14      if ( ntohs(eth->h_proto) >= 1536 )
15           return  eth->h_proto;   
16 }

6、从一个数据包(struct sk_buff)中提取源MAC地址:

int eth_header_parse(struct sk_buff *skb, u8 *haddr)

EXPORT_SYMBOL(eth_header_parse);

skb : 接收到的数据包;

haddr : 用于存放从接收的数据包中提取的硬件地址;

1 int  eth_header_parse( struct  sk_buff *skb, u8 *haddr)
2 {
3     struct  ethhdr *eth = eth_hdr(skb);
4     memcpy (haddr, eth->h_source, ETH_ALEN);  //可知haddr中存放的是源MAC地址;
5     return  ETH_ALEN;
6 }

7、在struct ethhdr中MAC地址为6个字节,并不是我们常见的MAC字符串地址,那么如果将6字节的MAC地址转化为我们常见的MAC字符串地址,使用下面这个函数:

char *print_mac(char *buffer, const unsigned char *addr);

EXPORT_SYMBOL(print_mac);

buffer : 为MAC字符串地址存放的地方;

addr : 为6字节MAC地址;

1 char  *print_mac( char  *buffer,  const  unsigned  char  *addr)
2 {
3     // MAC_BUF_SIZE = 18
4     // ETH_ALEN = 6
5     //_format_mac_addr(buffer, MAC_BUF_SIZE, addr, ETH_ALEN);
6     sysfs_format_mac_addr(buffer, MAC_BUF_SIZE, addr, ETH_ALEN);
7     return buffer; 
8  }

 

8、重新设置一个网络设备的MAC地址:

int eth_mac_addr(struct net_device *dev, void *p);

EXPORT_SYMBOL(eth_mac_addr);

dev : 为将要被设置的网络设备;

p : 为socket address;

 1 int  eth_mac_addr( struct  net_device *dev,  void  *p)
 2 {
 3      struct  sockaddr *addr = p;
 4      
 5      //用于判断网络设备是否正在运行
 6      if (netif_running(dev))
 7         return  -EBUSY;
 8         
 9      if ( !is_valid_ether_addr(addr->sa_data) )
10         return  -ETHADDRNOTAVAIL;
11       
12      memcpy (dev->dev_addr, addr->sa_data, ETH_ALEN);
13      return  0;
14 }

9、对一个struct net_device以太网网络设备进行初始化:

void ether_setup(struct net_device *dev);

 EXPORT_SYMBOL(ether_setup);

 1 const struct header_ops eth_header_ops ____cacheline_aligned = {
 2     .create = eth_header,
 3     .parse = eth_header_parse,
 4     .rebuild = eth_rebuild_header,
 5     .cache = eth_header_cache,
 6     .cache_update = eth_header_cache_update,
 7 };
 8 
 9 void ether_setup(struct net_device *dev)
10 {
11     dev->header_ops = &eth_header_ops;
12     dev->type = ARPHRD_ETHER;
13     dev->hard_header_len = ETH_HLEN;
14     dev->mtu = ETH_DATA_LEN;
15     dev->addr_len = ETH_ALEN;
16     dev->tx_queue_len = 1000;/*Ethernet wants good queues*/
17     dev->flags = IFF_BROADCAST | IFF_MULTICAST;
18     meset(dev->broadcast,0xFF,ETH_ALEN);
19 }

10、分配一个以太网网络设备,并对其进行初始化:

struct net_device *alloc_etherdev_mq(int sizeof_priv, u32 queue_count)

 EXPORT_SYMBOL(alloc_etherdev_mq);

1 struct  net_device *alloc_etherdev_mq( int  sizeof_priv, unsigned  int  queue_count)
2 {
3     // ether_setup为对分配的struct net_device进行初始化的函数;
4     //这个ether_setup是内核的导出函数,可以直接使用;
5      return  alloc_netdev_mq(sizeof_priv,  "eth%d" , ether_setup, queue_count);
6 }
7  
8 #define alloc_etherdev(sizeof_priv)  alloc_etherdev_mq(sizeof_priv, 1)

11、struct ethhdr中的MAC地址的判断:

 1 /* 用于判断一个MAC地址是否为零*/
 2 static  inline  int  is_zero_ether_addr( const  u8 *addr)
 3 {
 4     return  !(addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]);
 5 }
 6 
 7 /*用于判断addr中的MAC地址是否是组播MAC地址*/
 8 static  inline  int  is_multicast_ether_addr( const  u8 *addr)
 9 {
10     //组播MAC地址的判断方法:如果一个MAC地址的最低一位是1的话,则这个MAC地址为组播MAC地址;
11      return  (0x01 & addr[0]); 
12 }
13 
14 /*用于判断addr中的MAC地址是否是广播地址*/
15 static  inline  int  is_broadcast_ether_addr( const  u8 *addr)
16 {
17      return  ( addr[0] & addr[1] & addr[2] & addr[3] & addr[4] & addr[5] ) == 0xff;
18 }
19 
20 /*用于判断addr中的MAC地址是否是有效的MAC地址*/
21 static  inline  int  is_valid_ether_addr( const  u8 *addr)
22 {
23     //既不是组播地址,也不为0的MAC地址为有效的MAC地址;
24     return  !is_multicast_ether_addr(addr) && !is_zero_ether_addr(addr);
25 }
26 
27 /*用于软件随机产生一个MAC地址,然后存放与addr之中*/
28 static  inline  void  random_ether_addr(u8 *addr)
29 {
30       get_random_bytes(addr, ETH_ALEN);
31       addr[0] & = 0xfe;
32       addr[0] |= 0x02;  // IEEE802本地MAC地址
33 }
34 
35 /*用于判断addr中MAC地址是否是IEEE802中的本地MAC地址*/
36 static  inline  int  is_local_ether_addr( const  u8 *addr)
37 {
38      return  (0x02 & addr[0]);
39 }
40 
41 /*
42     关于IEEE802 MAC地址的须知:
43     IEEE802 LAN6字节MAC地址是目前广泛使用的LAN物理地址。IEEE802规定LAN地址字段的第一个字节的最低位表示I/G(Individual /Group)比特,即单地址/组地址比特。当它为“0”时,表示它代表一个单播地址,而这个位是“1”时,表示它代表一个组地址。
44    IEEE802规定LAN地址字段的第一个字节的最低第二位表示G/L(Globe/Local)比特,即全球/本地比特。当这个比特为“0”时,表 示全球管理,物理地址是由全球局域网地址的法定管理机构统一管理,全球管理地址在全球范围内不会发生地址冲突。当这个比特为“1”时,就是本地管理,局域 网管理员可以任意分配局部管理的网络上的地址,只要在自己网络中地址唯一不产生冲突即可,对外则没有意义,局部管理很少使用。
45   在6个字节的其他46个比特用来标识一个特定的MAC地址,46位的地址空间可表示约70万亿个地址,可以保证全球地址的唯一性。
46 */   
47 
48 /*用于比较两个MAC地址是否相等,相等返回0,不相等返回1*/
49 static  inline  unsigned compare_ether_addr( const  u8 *addr1,  const  u8 *addr2)
50 {
51      const  u16 *a = ( const  u16*)addr1;
52      const  u16 *b = ( const  u16*)addr2;
53      
54      return  ( (a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2]) ) != 0;
55 }

 

二、struct iphdr 结构体

 1 struct iphdr {
 2 #if defined(__LITTLE_ENDIAN_BITFIELD)
 3         __u8    ihl:4,
 4                 version:4;
 5 #elif defined (__BIG_ENDIAN_BITFIELD)
 6         __u8    version:4,
 7                 ihl:4;
 8 #else
 9 #error  "Please fix <asm/byteorder.h>"
10 #endif
11         __u8    tos;
12         __be16  tot_len;
13         __be16  id;
14         __be16  frag_off;
15         __u8    ttl;
16         __u8    protocol;
17         __sum16 check;
18         __be32  saddr;
19         __be32  daddr;
20         /*The options start here. */
21 };

 

iphdr->version

版本(4位),目前的协议版本号位4,也称之为IPv4

iphdr->ihl

首部长度(4位),首部长度是指IP层头部占32bit字的数目,也就是IP层头部包含多少个4字节(32b),包括任何选项,由于它是一个4bit(最大表示15)字段,因此首部最长位60个字节.普通IP数据报字段的值为5 ==》5*32/8=20Bytes

iphdr->tos

服务类型字段(8位):服务类型(TOS)字段包括一个3bit的优先权字段(已被忽略),4bit的TOS子字段和1bit未用位但必须置0。4bit的TOS子字段分别表示最小时延、最大吞吐量、最高可靠性和最小费用。4bit中只能设置1bit。如果4bit均为0表示这是一般服务。

iphdr->tot_len

总长度字段(16)位指的是整个IP数据包的长度,以字节位单位。利用首部长度字段和总长度字段,就可以知道IP数据报中数据内容的起始位置和长度。由于该字段长1bit,所以IP数据包最长可长达65535字节。

总长度字段是IP首部中必要的内容,因为一些数据链路(如以太网)需要填充一些数据以达到最小长度。尽管以太网的最小帧长为46字节,但IP数据可能更短。如果没有总长度字段,那么IP层就不知道46字节中有多少是IP数据报的内容。

iphdr->id

标识字段(16bit)唯一地标识主机发送地每一份数据报,通常每发送一份报文他的值就加1。

iphdr->frag_off

frag_off低13位

标识分段偏移(Fragment offset)域指明了该分段在当前数据报中的什么位置上。除了一个数据报的最后一个分段以外,其他所有的分段(分片)必须是8字节的倍数。这是8字节是基本分段单位。由于该域有13个位,所以每个数据报最多有8192个分段。因此最大数据报长度为65536字节,比iphdr->tot_len域还大1。

frag_off高3位:(从高至低依次是 0 DF MF)

l 比特0保留,必须为0;

l 比特1是“更多分片”(MF—More Fragment)标志。除了最后一片外,其他每个组成数据报的片都要把该比特置1.

l 比特2是“部分片”(DF—Don’t Fragment)标志,如果将这一比特置1,IP将不对数据报进行分片,这是如果需要进行分片的数据报到来,会丢弃此数据报并发送一个ICMP差错报文给起始端。

iphdr->ttl

TTL(Time to live)8位,生存时间字段设置了数据报可以经过的最多路由器数。它指定了数据报的生存时间。TTl的初始值由源主机设置(通常为32或64),一旦经过一个处理它的路由器,它的值就减去1。当该字段值为0时,数据报就被丢弃,并发送ICMP报文通知源主机。
TTL(Time to live)域是一个用于限制分组生存期的计数器。这里的计数单位为秒,因此最大生存期为255s。在每一跳上该计数器必须被递减。而且数据报在一台路由器上排队时间较长时,该计数器必须被多倍递减。在实践中,当它递减到0时,分组会被丢弃,路由器给源主机发送一个警告分组。此项特性可以避免数据报长时间地逗留在网络中。

iphdr->protocol

协议字段(8位):根据它可以识别是哪个协议向IP传送数据。

当网络层组装完成一个完整地数据报之后,他需要知道该如何对它进行处理。协议(Protocol)域指明了该将它交给哪个传输进程。TCP或者UDP或者其他协议。

iphdr->check

首部校验和字段(16)位时根据IP首部计算的校验和码。他不对首部后面的数据进行计算。ICMP、IGMP、UDP、TCP在它们各自的首部中均含有同时覆盖首部和数据校验和码。

为了计算一份数据报的IP校验和,首先把校验和字段置为0。然后对首部中每个16bit进行二进制反码求和(整个首部看出时一串16bie的字组成),结果存在校验和字段中。当收到一份IP数据报后,同样对首部中的每个16bit进行二进制反码求和。由于接收方在计算过程中包含了发送方存在首部中的校验和,因此如果首部在传输过程中没有发生任何差错,那么接收方计算的结果应该全为1。否则就意味着数据在传输过程中发生错误,IP就会丢弃收到的数据报。但是不生成差错报文,由上层区发现丢失的数据报并进行重传。

iphdr->saddr

32源IP地址

iphdr->daddr

32位目的IP地址

网络字节序

4字节的32bit值以下面的次数传输:

首先是0~7bit

其次是8~15bit

然后试16~23bit

最后是24~21bit

这种传输次数称之为big-endian字节序。由于TCP/IP首部中所有的为二进制整数在网络传输中都要求以这种次序,因此它又被称为网络字节序。

2、获取

 1 static inline struct iphdr *ip_hdr(const struct sk_buff *skb)
 2 {
 3          return (struct iphdr *)skb_network_header(skb);
 4 }
 5 
 6 static inline struct iphdr *inner_ip_hdr(const struct sk_buff *skb)
 7 {
 8          return (struct iphdr *)skb_inner_network_header(skb);
 9 }
10 
11 static inline struct iphdr *ipip_hdr(const struct sk_buff *skb)
12 {
13          return (struct iphdr *)skb_transport_header(skb);
14 }

#include <linux/ip.h>
struct iphdr *iph;
iph = ip_hdr(skb);

 

三、struct udphdr 结构体

struct udphdr {
        __be16  source;
        __be16  dest;
        __be16  len;
        __sum16 check;
};

static inline struct udphdr *udp_hdr(const struct sk_buff *skb)
{
        return (struct udphdr *)skb_transport_header(skb);
}

static inline struct udphdr *inner_udp_hdr(const struct sk_buff *skb)
{
        return (struct udphdr *)skb_inner_transport_header(skb);
}

从struct sk_buff *skb获取udp头部

#include <linux/udp.h>
struct udphdr *udph;
udph = udp_hdr(skb);

结果:

  获取的udph是错误的udp头

原因:

  因为此时sk_buff的transport_header并没有指向正确的udp头,而是和network_header一同指向了ip头。

正确获取udp头部方式:

 1、通过ip头计算udp头  

1 struct udphdr *udph;
2 udph = (struct udphdr *) ((u8 *) iph + (iph->ihl << 2));

  2、先设置transport_header指向正确的udp头,再用udp_hdr()获取

1 struct udphdr *udph;
2 skb_set_transport_header(skb, sizeof(struct iphdr));  //iph->ihl << 2
3 udph = udp_hdr(skb);

实例:

 1 unsigned int hook_mark1(unsigned int hooknum, struct sk_buff *skb,const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *))
 2 {
 3     struct iphdr *iph;
 4     struct udphdr *udph1;
 5     struct udphdr *udph2;
 6  
 7     iph = ip_hdr(skb);
 8     if (iph->protocol == 17){
 9         iph = ip_hdr(skb);
10         udph1 = udp_hdr(skb);
11         udph2 = (struct udphdr *) ((u8 *) iph + (iph->ihl << 2));
12         printk("001 iph:%p, udph1:%p, udph2:%p\n", iph, udph1, udph2);
13  
14         skb_set_transport_header(skb, sizeof(struct iphdr)); 
15  
16         iph = ip_hdr(skb);
17         udph1 = udp_hdr(skb);
18         udph2 = (struct udphdr *) ((u8 *) iph + (iph->ihl << 2));
19         printk("002 iph:%p, udph1:%p, udph2:%p\n", iph, udph1, udph2);
20     }
21     return NF_ACCEPT;
22 }

 

四、struct tcphdr 结构体

 1 struct tcphdr {
 2         __be16  source;
 3         __be16  dest;
 4         __be32  seq;
 5         __be32  ack_seq;
 6 #if defined(__LITTLE_ENDIAN_BITFIELD)
 7         __u16   res1:4,
 8                 doff:4,
 9                 fin:1,
10                 syn:1,
11                 rst:1,
12                 psh:1,
13                 ack:1,
14                 urg:1,
15                 ece:1,
16                 cwr:1;
17 #elif defined(__BIG_ENDIAN_BITFIELD)
18         __u16   doff:4,
19                 res1:4,
20                 cwr:1,
21                 ece:1,
22                 urg:1,
23                 ack:1,
24                 psh:1,
25                 rst:1,
26                 syn:1,
27                 fin:1;
28 #else
29 #error  "Adjust your <asm/byteorder.h> defines"
30 #endif
31         __be16  window;
32         __sum16 check;
33         __be16  urg_ptr;
34 };
35 
36 static inline struct tcphdr *tcp_hdr(const struct sk_buff *skb)
37 {
38         return (struct tcphdr *)skb_transport_header(skb);
39 }
40 
41 static inline unsigned int __tcp_hdrlen(const struct tcphdr *th)
42 {
43         return th->doff * 4;
44 }
45 
46 static inline unsigned int tcp_hdrlen(const struct sk_buff *skb)
47 {
48         return __tcp_hdrlen(tcp_hdr(skb));
49 }
50 
51 static inline struct tcphdr *inner_tcp_hdr(const struct sk_buff *skb)
52 {
53         return (struct tcphdr *)skb_inner_transport_header(skb);
54 }
55 
56 static inline unsigned int inner_tcp_hdrlen(const struct sk_buff *skb)
57 {
58         return inner_tcp_hdr(skb)->doff * 4;
59 }
60 
61 static inline unsigned int tcp_optlen(const struct sk_buff *skb)
62 {
63         return (tcp_hdr(skb)->doff - 5) * 4;
64 }

 

 

 

tcphdr->source

16位源端口

tcphdr->dest

16位目的端口

tcphdr->seq

表示此次发送的数据在整个报文段中的起始字节数。序号是32位bit的无符号数。为了安全起见,它的初始值是一个随机生成的数,它到达32位最大值后,又从零开始。

tcphdr->ack_seq

指定的是下一个期望接收的字节,而不是已经正确接收的最后一个字节。

tcphdr->doff

TCP头长度,指明了TCP头部包含了多少个32位的字。此信息的必须的,因为option域的长度是可变的,所有整个TCP头部的长度也是可变的。从技术上讲这个域实际上指明了数据部分在段内的起始地址(以32位字作为单位进行计量),因为这个数值正好是按字为单位的TCP头部的长度,所以,二者的效果是等效的。

tcphdr->res1

保留位

tcphdr->window

16位滑动窗口大小,单位为字节,起始于确认序号字段指明的值,这个值是接收端期望接收的字节数,其最大值为63353字节。

TCP中的流量控制是同一个可变大小的滑动窗口来完成的。window域指定了从被确认的字节算起可以接收多少个字节。window=0也是合法的,相当于说到现在为止多达ack_seq-1个字节已经接收到了,但是接收放现在状态不佳,需要休息一下,等一会再接收更多的数据。以后接收方可以通过发送一个同样ack_seq但是window不为0的数据段,告诉发送方继续发送数据段。

tcphdr->check

校验和,覆盖了整个tcp报文端,是一个强制性的字段,一定是由发送端计算和存储,并由接收端进行验证。

tcphdr->urg_ptr

这个域被用来指示紧急数据在当前数据段中的为止,它是一个相当于当前序列号的字节偏移量。这个设置可以代替中断信息。

fin、syn、rst、psh、ack、urg为6个标志位,含义如下:

tcphdr->fin :释放一个连接,它表示发送方已经没有数据要传输了。

tcphdr->syn :同步序号,用来发送一个连接。syn被用于建立连接的过程,在连接请求中,syn=1;ack=0表示该数据段没有使用捎带的确认域。连接应答捎带了一个确认,所以有syn=1;ack=1。本质上,syn位被用于表示connection request和connection accepted,然而进一步用ack位来区分这两种情况。

tcphdr->ret :该位用于重置一个混乱的连接,之所以混乱,可能是因为主机崩溃或者其他原因。该位也可以被用来拒绝一个无效的数据段,或者拒绝一个连接请求,一般而言,如果你得到的数据段设置了rst位,说明你这一端有了问题。

tcphdr->ack :ack位被设置为1表示tcphdr->ack_seq是有效的,如果ack为0,则表示该数据段不包含确认信息,所以tcphdr->ack_seq域应该被忽略。

tcphdr->urg :紧急指针有效

tcphdr->ece :用途暂时不明

tcphdr->cwr :用途暂时不明

从struct sk_buff *skb获取udp头部

#include <linux/tcp.h>
struct tcphdr *tcph;
tcph = tcp_hdr(skb);

结果:

  获取的tcph是错误的udp头

原因:

  因为此时sk_buff的transport_header并没有指向正确的tcp头,而是和network_header一同指向了ip头。

正确获取udp头部方式:

1、通过ip头计算tcp头

1  struct tcphdr *tcph;
2  tcph = (struct tcphdr *) ((u8 *) iph + (iph->ihl << 2));

2、先设置transport_header指向正确的tcp头,再用tcp_hdr()获取

1 struct tcphdr *tcph;
2 skb_set_transport_header(skb, sizeof(struct iphdr));  //iph->ihl << 2
3 udph = tcp_hdr(skb);

 

 



 

 

posted on 2022-10-22 17:57  背影_墨白  阅读(3268)  评论(0编辑  收藏  举报