在linux系统中,使用struct ethhdr结构体来表示以太网帧的头部。这个struct ethhdr结构体位于#include
#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