一、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 = ð_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);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律