一、MAC帧 格式
MAC帧是属于链路层,网卡发送数据的格式。
MAC帧主要有两种格式,一种是以太网V2标准,一种是IEEE 802.3,常用的是前者。
DMAC(Destination MAC)是目的MAC地址。DMAC字段长度为6个字节,标识帧的接收者。
SMAC(Source MAC)是源MAC地址。SMAC字段长度为6个字节,标识帧的发送者。
Type(类型字段)用于标识数据字段中包含的高层协议,该字段长度为2个字节。类型字段值为 0x0800的帧代表IP协议帧;类型字段值为0806的帧代表ARP协议帧。
Data(数据字段)是网络层数据,最小长度必须为46字节以保证帧长至少为64字节,数据字段的最大长度为1500字节。
FCS(循环冗余校验字段)提供了一种错误检测机制。该字段长度为4个字节。
类型只有2个,主要是由于目前的协议栈
- 要么就是应用层发送数据时使用底层的ip协议
- 要么就是在发送数据时未找到对方信息ip/mac,这时候就需要通过arp协议寻找对方信息,寻找到以后保存在协议栈中
下面是MAC头部信息的数据信息: (因为是调用ARP层函数接口拷贝硬件header 信息,目前还没有对arp 协议深入研究,只能通过下面头文件反推MAC头部信息)
#define ETH_ALEN 6 /* Octets in one ethernet addr */
struct ethhdr {
unsigned char h_dest[ETH_ALEN]; /* destination eth addr */
unsigned char h_source[ETH_ALEN]; /* source ether addr */
__be16 h_proto; /* packet type ID field */
} __attribute__((packed));
组装后的包:
MAC头部信息,在IP层是如何添加的?
MAC头部信息是在arp 协议接口里面进行赋值的。下面展示一下具体源码:
ip_output->ip_finish_ouput->ip_finish_output2->neigh_output
static int ip_finish_output2(struct net *net, struct sock *sk, struct sk_buff *skb)
{
struct dst_entry *dst = skb_dst(skb);
struct rtable *rt = (struct rtable *)dst;
struct net_device *dev = dst->dev;
unsigned int hh_len = LL_RESERVED_SPACE(dev);
struct neighbour *neigh;
bool is_v6gw = false;
if (rt->rt_type == RTN_MULTICAST) {
IP_UPD_PO_STATS(net, IPSTATS_MIB_OUTMCAST, skb->len);
} else if (rt->rt_type == RTN_BROADCAST)
IP_UPD_PO_STATS(net, IPSTATS_MIB_OUTBCAST, skb->len);
/* Be paranoid, rather than too clever. */
if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {
struct sk_buff *skb2;
skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));
if (!skb2) {
kfree_skb(skb);
return -ENOMEM;
}
if (skb->sk)
skb_set_owner_w(skb2, skb->sk);
consume_skb(skb);
skb = skb2;
}
if (lwtunnel_xmit_redirect(dst->lwtstate)) {
int res = lwtunnel_xmit(skb);
if (res < 0 || res == LWTUNNEL_XMIT_DONE)
return res;
}
rcu_read_lock_bh();
neigh = ip_neigh_for_gw(rt, skb, &is_v6gw);
if (!IS_ERR(neigh)) {
int res;
sock_confirm_neigh(skb, neigh);
/* if crossing protocols, can not use the cached header */
res = neigh_output(neigh, skb, is_v6gw); // 调用arp 层的函数接口
rcu_read_unlock_bh();
return res;
}
rcu_read_unlock_bh();
net_dbg_ratelimited("%s: No header cache and no neighbour!\n",
__func__);
kfree_skb(skb);
return -EINVAL;
}
在ip_finish_oupt 中,是调用了 GSO 的函数接口,在ip 层分段之后。接着开始在ip_finish_output2 中调用arp函数接口,把MAC头部信息添加上。因为ARP层有ARP表,可以查询目的地址。MAC头部信息添加之后,在调用 网络设备接口层函数,其实是回调加载驱动的函数。
接着看ARP 层的函数接口:neigh_output
static inline int neigh_output(struct neighbour *n, struct sk_buff *skb,
bool skip_cache)
{
const struct hh_cache *hh = &n->hh; // 硬件头部信息缓存
/* n->nud_state and hh->hh_len could be changed under us.
* neigh_hh_output() is taking care of the race later.
*/
if (!skip_cache &&
(READ_ONCE(n->nud_state) & NUD_CONNECTED) &&
READ_ONCE(hh->hh_len))
return neigh_hh_output(hh, skb); //调用硬件头部信息缓存
return n->output(n, skb);
}
接着neigh_hh_output
static inline int neigh_hh_output(const struct hh_cache *hh, struct sk_buff *skb)
{
unsigned int hh_alen = 0;
unsigned int seq;
unsigned int hh_len;
do {
seq = read_seqbegin(&hh->hh_lock);
hh_len = READ_ONCE(hh->hh_len);
if (likely(hh_len <= HH_DATA_MOD)) {
hh_alen = HH_DATA_MOD;
/* skb_push() would proceed silently if we have room for
* the unaligned size but not for the aligned size:
* check headroom explicitly.
*/
if (likely(skb_headroom(skb) >= HH_DATA_MOD)) {
/* this is inlined by gcc */
memcpy(skb->data - HH_DATA_MOD, hh->hh_data,
HH_DATA_MOD);// 拷贝硬件头部缓存信息
}
} else {
hh_alen = HH_DATA_ALIGN(hh_len);
if (likely(skb_headroom(skb) >= hh_alen)) {
memcpy(skb->data - hh_alen, hh->hh_data,
hh_alen);
}
}
} while (read_seqretry(&hh->hh_lock, seq));
if (WARN_ON_ONCE(skb_headroom(skb) < hh_alen)) {
kfree_skb(skb);
return NET_XMIT_DROP; //headroom 空间不够,会丢包
}
__skb_push(skb, hh_len); //移动data 的指针
return dev_queue_xmit(skb);// 发送到网络设备接口层,这是发送到硬件驱动了
}
严格一点来说,MAC头部信息其实是在ARP协议层里完成的。
(自己看代码发现,不一定准确)拿lo 网卡来说,调用ARP的函数接口如下:
第一种情况:
调用 neigh_output 函数中的 n->output(n, skb);
然后output 声明如下:
static const struct neigh_ops arp_direct_ops = {
.family = AF_INET,
.output = neigh_direct_output,
.connected_output = neigh_direct_output,
};
接着neigh_resolve_output
int neigh_direct_output(struct neighbour *neigh, struct sk_buff *skb)
{
return dev_queue_xmit(skb);
}
EXPORT_SYMBOL(neigh_direct_output);
这样是直接本地回环发送数据包。
第二种情况:
output 声明如下:
static const struct neigh_ops arp_generic_ops = {
.family = AF_INET,
.solicit = arp_solicit,
.error_report = arp_error_report,
.output = neigh_resolve_output,
.connected_output = neigh_connected_output,
};
接着neigh_resolve_output
/* Slow and careful. */
int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb)
{
int rc = 0;
if (!neigh_event_send(neigh, skb)) {
int err;
struct net_device *dev = neigh->dev;
unsigned int seq;
if (dev->header_ops->cache && !READ_ONCE(neigh->hh.hh_len))
neigh_hh_init(neigh);
do {
__skb_pull(skb, skb_network_offset(skb));
seq = read_seqbegin(&neigh->ha_lock);
err = dev_hard_header(skb, dev, ntohs(skb->protocol),
neigh->ha, NULL, skb->len);// 硬件设备头部信息MAC
} while (read_seqretry(&neigh->ha_lock, seq));
if (err >= 0)
rc = dev_queue_xmit(skb);
else
goto out_kfree_skb;
}
out:
return rc;
out_kfree_skb:
rc = -EINVAL;
kfree_skb(skb);
goto out;
}
EXPORT_SYMBOL(neigh_resolve_output);
接着dev_hard_header
static inline int dev_hard_header(struct sk_buff *skb, struct net_device *dev,
unsigned short type,
const void *daddr, const void *saddr,
unsigned int len)
{
if (!dev->header_ops || !dev->header_ops->create)
return 0;
return dev->header_ops->create(skb, dev, type, daddr, saddr, len);
}
其实就是调用 header_ops 的定义好的函数接口,声明如下:
路径:kernel/net/ethernet/eth.c:347
const struct header_ops eth_header_ops ____cacheline_aligned = {
.create = eth_header,
.parse = eth_header_parse,
.cache = eth_header_cache,
.cache_update = eth_header_cache_update,
.parse_protocol = eth_header_parse_protocol,
};
接着看eth_header
/**
* eth_header - create the Ethernet header
* @skb: buffer to alter
* @dev: source device
* @type: Ethernet type field
* @daddr: destination address (NULL leave destination address)
* @saddr: source address (NULL use device source address)
* @len: packet length (<= skb->len)
*
*
* Set the protocol type. For a packet of type ETH_P_802_3/2 we put the length
* in here instead.
*/
int eth_header(struct sk_buff *skb, struct net_device *dev,
unsigned short type,
const void *daddr, const void *saddr, unsigned int len)
{
struct ethhdr *eth = skb_push(skb, ETH_HLEN);
if (type != ETH_P_802_3 && type != ETH_P_802_2)
eth->h_proto = htons(type);
else
eth->h_proto = htons(len);
/*
* Set the source hardware address.
*/
if (!saddr)
saddr = dev->dev_addr;
memcpy(eth->h_source, saddr, ETH_ALEN);
if (daddr) {
memcpy(eth->h_dest, daddr, ETH_ALEN);
return ETH_HLEN;
}
/*
* Anyway, the loopback-device should never use this function...
*/
if (dev->flags & (IFF_LOOPBACK | IFF_NOARP)) {
eth_zero_addr(eth->h_dest);
return ETH_HLEN;
}
return -ETH_HLEN;
}
EXPORT_SYMBOL(eth_header);
这就完成了MAC头部信息的拷贝。
在IP层,只是把mac_header 空间保留出来,在后面arp 层,才是实际赋值数据的操作。
二、IP 头部结构 的定义
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 ihl:4,
version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
__u8 version:4,
ihl:4;版本信息(前4位),头长度(后4位)
#else
#error "Please fix <asm/byteorder.h>"
#endif
__u8 tos;//服务类型8位
__be16 tot_len;//数据包长度
__be16 id;//数据包标识
__be16 frag_off;//分片使用
__u8 ttl;//存活时间
__u8 protocol;//协议类型
__sum16 check;//校验和
__be32 saddr; //源ip
__be32 daddr;//目的ip
/*The options start here. */
};
注意:ip报文头20个字节,但是在实际的数据包中可能长度大于20(有一些选项)
三、tcp 头部结构定义
struct tcphdr {
__be16 source; // 源端口号16bit
__be16 dest; // 目的端口号16bit
__be32 seq; // 序列号32bit
__be32 ack_seq; // 确认号32bit
#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;// 前4位:TCP头长度;中6位:保留;后6位:标志位
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
__be16 window; // 窗口大小16bit
__sum16 check; // 检验和16bit
__be16 urg_ptr; // 紧急数据偏移量16bit
};
注意:tcp报文头20个字节,但是在实际的数据包中可能长度大于20(有一些选项)
四、udp 头部结构定义
struct udphdr {
__be16 source; // 源端口号16bit
__be16 dest; // 目的端口号16bit
__be16 len; // 数据包长度16bit
__sum16 check; // 校验和16bit
};
注意:udp报文头8个字节
五、arp 报文格式
上面28字节的具体内容:
- 硬件类型:占两字节,表示ARP报文可以在哪种类型的网络上传输,值为1时表示为以太网地址。
- 上层协议类型:占两字节,表示硬件地址要映射的协议地址类型,映射IP地址时的值为0x0800。
- MAC地址长度:占一字节,标识MAC地址长度,以字节为单位,此处为6。
- IP协议地址长度:占一字节,标识IP得知长度,以字节为单位,此处为4。
- 操作类型:占2字节,指定本次ARP报文类型。1标识ARP请求报文,2标识ARP应答报文。
- 源MAC地址:占6字节,标识发送设备的硬件地址。
- 源IP地址:占4字节,标识发送方设备的IP地址。
- 目的MAC地址:占6字节,表示接收方设备的硬件地址,在请求报文中该字段值全为0,即00-00-00-00-00-00,表示任意地址,因为现在不知道这个MAC地址。
- 目的IP地址:占4字节,表示接受方的IP地址。
arp报文 在组装,才能调用网卡驱动。
ARP报文不是直接在网络层上发送的,它还是需要向下传输到数据链路层,所以当ARP报文传输到数据链路层之后,需要再次进行封装。以以太网为例,ARP报文传输到以太网数据链路层后会形成ARP帧。ARP帧如下图所示,他就是在ARP报文前面加了一个以太网帧头。
封装之后,术语是ARP帧,才能调用网卡驱动进行发送。