struct ethhdr结构体详解

    在linux系统中,使用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))  //用于告诉编译器不要对这个结构体中的缝隙部分进行填充操作;

网络层所使用的协议类型有(常见的类型):

#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协议

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

{

return (struct ethhdr *)skb_mac_header(skb);

}

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

#define MAC_FMT  "%02x:%02x:%02x:%02x:%02x:%02x"

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

#define DECLARE_MAC_BUF(var)  char var[MAC_BUF_LEN] //定义了一个MAC字符缓存

1.创建一个以太网头结构体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

int eth_header(struct sk_buff *skb, struct net_device *dev, u16 type, void *daddr, void *saddr, int len)
{
    //将skb->data = skb->data + ETH_ALEN;
    struct ethhdr *eth = (struct ethhdr*)skb_push(skb, ETH_ALEN);
    
    if(type != ETH_P_802_3)
       eth->proto = htons(type); // htons()将本地类型转换为网络类型
     else
       eth->proto = htons(len);
    
    //如果 saddr = NULL的话,以太网帧头中的源MAC地址为dev的MAC地址   
    if(!saddr)
       saddr = dev->dev_addr;
    memcpy(eth->saddr, saddr, ETH_ALEN);
    
    if(daddr)
    {
       memcpy(eth->daddr, daddr, ETH_ALEN);
       return ETH_HLEN ; //返回值为14
    }
    
    return -ETH_HLEN;
}

   

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

      __be16 eth_type_trans(struct sk_buff *skb,

                             struct net_device *dev);

     EXPORT_SYMBOL(eth_type_trans);

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

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

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

__be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev)
{
    struct ethhdr *eth;
    
    skb->dev = dev;
    eth = eth_hdr(skb);
    
    if(netdev_uses_dsa_tags(dev))
         return htons(ETH_P_DSA);
         
    if(netdev_uses_trailer_tags(dev))
         return htons(ETH_P_TRAILER);
         
    if( ntohs(eth->h_proto) >= 1536 )
         return eth->h_proto;   
}

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

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

    EXPORT_SYMBOL(eth_header_parse);

    skb : 接收到的数据包;

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

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


    4.在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地址;

char *print_mac(char *buffer, const unsigned char *addr)
{
   // MAC_BUF_SIZE = 18
   // ETH_ALEN = 6
   _format_mac_addr(buffer, MAC_BUF_SIZE, addr, ETH_ALEN);
   
   return buffer;
}

5.重新设置一个网络设备的MAC地址:

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

     EXPORT_SYMBOL(eth_mac_addr);

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

     p   : 为socket address;

int eth_mac_addr(struct net_device *dev, void *p)
{
    struct sockaddr *addr = p;
    
    //用于判断网络设备是否正在运行
    if(netif_running(dev))
       return -EBUSY;
       
    if( !is_valid_ether_addr(addr->sa_data) )
       return -ETHADDRNOTAVAIL;
     
    memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN);
    return 0;
}

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

      void ether_setup(struct net_device *dev);

      EXPORT_SYMBOL(ether_setup);

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

      struct net_device *alloc_etherdev_mq(int sizeof_priv,

                       u32 queue_count)

      EXPORT_SYMBOL(alloc_etherdev_mq);

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

#define alloc_etherdev(sizeof_priv)  alloc_etherdev_mq(sizeof_priv, 1)

下面的这些函数用于struct ethhdr中的MAC地址的判断:

   

    1.int is_zero_ether_addr(const u8 *addr);

          用于判断一个MAC地址是否为零;

static inline int is_zero_ether_addr(const u8 *addr)
{
   return !(addr[0] | addr[1] | addr[2] | addr[3] | addr[4] | addr[5]);
}

     2.int is_multicast_ether_addr(const u8 *addr)

           用于判断addr中的MAC地址是否是组播MAC地址;

static inline int is_multicast_ether_addr(const u8 *addr)
{
   //组播MAC地址的判断方法:如果一个MAC地址的最低一位是1的话,则这个MAC地址为组播MAC地址;
    return (0x01 & addr[0]); 
}

3.int is_broadcast_ether_addr(const u8 *addr)

            用于判断addr中的MAC地址是否是广播地址;

static inline int is_broadcast_ether_addr(const u8 *addr)
{
    return ( addr[0] & addr[1] & addr[2] & addr[3] & addr[4] & addr[5] ) == 0xff;
}

4. int is_valid_ether_addr(const u8* addr)

             用于判断addr中的MAC地址是否是有效的MAC地址;

static inline int is_valid_ether_addr(const u8 *addr)
{
   //既不是组播地址,也不为0的MAC地址为有效的MAC地址;
   return !is_multicast_ether_addr(addr) && !is_zero_ether_addr(addr);
}

5. void random_ether_addr(u8 *addr)

             用于软件随机产生一个MAC地址,然后存放与addr之中;

static inline void random_ether_addr(u8 *addr)
{
     get_random_bytes(addr, ETH_ALEN);
     addr[0] & = 0xfe;
     addr[0] |= 0x02; // IEEE802本地MAC地址
}

6.int is_local_ether_addr(const u8 *addr)

           用于判断addr中MAC地址是否是IEEE802中的本地MAC地址。

static inline int is_local_ether_addr(const u8 *addr)
{
    return (0x02 & addr[0]);
}

关于IEEE802 MAC地址的须知:

  IEEE802
LAN6字节MAC地址是目前广泛使用的LAN物理地址。IEEE802规定LAN地址字段的第一个字节的最低位表示I/G(Individual
/Group)比特,即单地址/组地址比特。当它为“0”时,表示它代表一个单播地址,而这个位是“1”时,表示它代表一个组地址。
  IEEE802规定LAN地址字段的第一个字节的最低第二位表示G/L(Globe/Local)比特,即全球/本地比特。当这个比特为“0”时,表
示全球管理,物理地址是由全球局域网地址的法定管理机构统一管理,全球管理地址在全球范围内不会发生地址冲突。当这个比特为“1”时,就是本地管理,局域
网管理员可以任意分配局部管理的网络上的地址,只要在自己网络中地址唯一不产生冲突即可,对外则没有意义,局部管理很少使用。
  在6个字节的其他46个比特用来标识一个特定的MAC地址,46位的地址空间可表示约70万亿个地址,可以保证全球地址的唯一性。  

7.unsigned compare_ether_addr(const u8 *addr1, const u8 *addr2)

           用于比较两个MAC地址是否相等,相等返回0,不相等返回1;

static inline unsigned compare_ether_addr(const u8 *addr1, const u8 *addr2)
{
    const u16 *a = (const u16*)addr1;
    const u16 *b = (const u16*)addr2;
    
    return ( (a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2]) ) != 0;
}

  以上的所有的函数可以通过 #include<linux/etherdevice.h>头文件,来直接使用。

时间: 2024-10-11 04:38:44

struct ethhdr结构体详解的相关文章

struct sk_buff结构体详解

struct sk_buff是linux网络系统中的核心结构体,linux网络中的所有数据包的封装以及解封装都是在这个结构体的基础上进行. struct sk_buff_head  {     struct sk_buff *next;     struct sk_buff *prev;          __u32 qlen;     spinlock_t lock; } struct sk_buff {     struct sk_buff *next;     struct sk_buff

struct socket结构体详解

在内核中为什么要有struct socket结构体呢?    struct socket结构体的作用是什么?    下面这个图,我觉得可以回答以上两个问题.      由这个图可知,内核中的进程可以通过使用struct socket结构体来访问linux内核中的网络系统中的传输层.网络层.数据链路层.也可以说struct socket是内核中的进程与内核中的网路系统的桥梁.   struct socket {      socket_state  state; // socket state  

linux内核中的struct rlimit结构体详解

   在linux内核中,对一个进程获取系统资源的数量进行了限制.那么linux内核是如何实现这种对一个进程的各种资源的限制呢?    linux使用struct rlimit结构体来实现的,rlimit是 resource limit的缩写.    struct rlimit           {               unsigned int rlim_cur;  /* soft limit */               unsigned int rlim_max;  /* ha

NetBios 的结构体详解(网络控制块NCB)

对之前网络基础编程用到控制块NCB进行介绍(补充): 在Win32环境下,使用VC++6.0进行NetBIOS程序开发时, 需要用到nb30.h文件和netapi32.lib静态链接库.前者定义了NetBIOS的所有函数声明和常数定义,后者定义了NetBIOS应用. Ncb的结构在nb30.h文件中定义.Ncb结构的定义:Ncb有64个字符,分为14个域(或称为字段)和一个10字节的保留域,表2.1显示了Ncb和它的域. 域名及字节偏移量说明. 各个命令详细说明: 1. 命令 NetBIOS命令

NetBios 的结构体详解

[NetBios 的结构体详解] 1.结构体. 2.命令 NetBIOS命令的使用方式有两种,即等待和非等待(或称为同步与异步)方式. 如果命令码的高阶位是0时是等待方式,命令将阻止提交命令的应用程序继续执行,直到该NetBIOS命令执行完毕后才允许这个应用程序继续执行.NetBIOS每次只能处理一个等待方式命令. 如果命令码的高阶位是二进制1时则是非等待方式,命令由NetBIOS在内部排队,并不阻止提交命令的应用程序继续执行.应用程序可通过对命令结束标志字段值的轮询来了解命令执行的状态(详见命

struct net_device网络设备结构体详解

在linux中使用struct net_device结构体来描述每一个网络设备.同时这个用来刻画网络设备的struct net_device结构体包含的字段非常的多,以至于内核的开发者都觉得在现在的linux内核中,这个struct net_device是一个大的错误.    在本篇文章中,只介绍struct net_device中的一些字段,其他的字段在以后使用的时候再说.    #define IFNAMSIZ 32    struct net_device    {        //用于

结构体详解

1 概述 C语言允许用户自己指定这样一种数据结构,它由不同类型的数据组合成一个整体,以便引用,这些组合在一个整体中的数据是互相联系的,这样的数据结构称为结构体,它相当于其它高级语言中记录. 声明一个结构休类型的一般形式如下: struct 结构体名 {成员列表}; 结构体名,用作结构体类型的标志,它又称 结构体标记,大括号内是该结构体中的各个成员,由它们组成一个结构体,对各成员都应进行类型声明如: 类型名 成员名; 也可以把 成员列表称为 域表,第一个成员也称为结构体中的一个域.成员名定名规则写

struct结构体详解

为什么要有结构体 结构体和其他类型基础数据类型一样,例如int类型,char类型 只不过结构体可以做成你想要的数据类型.以方便日后的使用. 在实际项目中,结构体是大量存在的.研发人员常使用结构体来封装一些属性来组成新的类型.由于C语言内部程序比较简单,研发人员通常使用结构体创造新的"属性",其目的是简化运算. 结构体在函数中的作用不是简便,其最主要的作用就是封装.封装的好处就是可以再次利用.让使用者不必关心这个是什么,只要根据定义使用就可以了. 在C语言中,可以定义结构体类型,将多个相

Linux下DIR,dirent,stat等结构体详解

摘要: 最近在看Linux下文件操作相关章节,遇到了这么几个结构体,被搞的晕乎乎的,今日有空,仔细研究了一下,受益匪浅. DIR结构体类似于FILE,是一个内部结构,以下几个函数用这个内部结构保存当前正在被读取的目录的有关信息(摘自<UNIX环境高级编程(第二版)>).函数 DIR *opendir(const char *pathname),即打开文件目录,返回的就是指向DIR结构体的指针,而该指针由以下几个函数使用: 最近在看Linux下文件操作相关章节,遇到了这么几个结构体,被搞的晕乎乎