sw_flow_key相当于流表的索引,对其哈希后可以找到对应的flow entry,从而找到action list。构建key是由 ovs_flow_extract函数完成的,它从以太网帧中提携相关信息构造sw_flow_key ,参数中skb->data指向的是以太帧头ether header,in_port是收到skb的端口号,最后俩是值-结果参数。
int ovs_flow_extract(struct sk_buff *skb, u16 in_port, struct sw_flow_key *key, int *key_lenp)
{
int error = 0;
int key_len = SW_FLOW_KEY_OFFSET(eth);
struct ethhdr *eth;
//key_len 存在的意义是每次填充一个域的时候才增加实际长度(offset+fieldsize);
memset(key, 0, sizeof(*key));
key->phy.priority = skb->priority; //QoS相关的;
if (OVS_CB(skb)->tun_key) //如果skb->cb 域中有tunnel的信息;
memcpy(&key->phy.tun.tun_key, OVS_CB(skb)->tun_key, sizeof(key->phy.tun.tun_key));
key->phy.in_port = in_port;
key->phy.skb_mark = skb_get_mark(skb); //这是防火墙(NetFilter)功能专用参数;
skb_reset_mac_header(skb);
//解析的同时也更新skb里面的头指针 skb->mac_header = skb->data;
//链路层,这里要保证至少有14B的ether header;
eth = eth_hdr(skb); //得到链路层协议头;
memcpy(key->eth.src, eth->h_source, ETH_ALEN);
memcpy(key->eth.dst, eth->h_dest, ETH_ALEN);
__skb_pull(skb, 2 * ETH_ALEN);
//向前推进skb->data 接下来的2B是以太类型;
if (vlan_tx_tag_present(skb))
key->eth.tci = htons(vlan_get_tci(skb));
else if (eth->h_proto == htons(ETH_P_8021Q))
if (unlikely(parse_vlan(skb, key)))
return -ENOMEM;
key->eth.type = parse_ethertype(skb); //从SNAP OUI后的16bit得到以太类型;
skb_reset_network_header(skb);
//skb->nh.raw = skb->data;在skb中代表L2,L3,L4的字段mac,nh,h都实现为联合,每个联合中的raw成员用于初始化;
__skb_push(skb, skb->data - skb_mac_header(skb));
//和前面的__skb_pull相反,缓存头增加12B的空间(skb->data - skb_mac_header(skb)=12),有啥作用??
//网络层-->
if (key->eth.type == htons(ETH_P_IP)) {
struct iphdr *nh;
__be16 offset;
key_len = SW_FLOW_KEY_OFFSET(ipv4.addr);
error = check_iphdr(skb);//在里面同时会设置 transport header;
nh = ip_hdr(skb);
//得到IP头结构体(struct iphdr *)skb_network_header(skb);iphdr具体细节看A1;
key->ipv4.addr.src = nh->saddr;
key->ipv4.addr.dst = nh->daddr;
key->ip.proto = nh->protocol;
key->ip.tos = nh->tos;
key->ip.ttl = nh->ttl;
offset = nh->frag_off & htons(IP_OFFSET);
//通过0x1FFF掩码运算得到13bit的片偏移字段值;
if (offset) {
key->ip.frag = OVS_FRAG_TYPE_LATER; //如果是后续的片段就直接返回 ,因为一个packet就可以提携出一个流特征;
goto out;
}
//否则offset=0,并且MF=1,说明是第一个fragment;SKB_GSO_UDP ??
if (nh->frag_off & htons(IP_MF) || skb_shinfo(skb)->gso_type & SKB_GSO_UDP)
key->ip.frag = OVS_FRAG_TYPE_FIRST;
//传输层-->
if (key->ip.proto == IPPROTO_TCP) {
key_len = SW_FLOW_KEY_OFFSET(ipv4.tp);
if (tcphdr_ok(skb)) { //*hdr_ok的主要作用是检查是否满足特定协议头的长度;
struct tcphdr *tcp = tcp_hdr(skb);
key->ipv4.tp.src = tcp->source; //获得TCP源目端口号;
key->ipv4.tp.dst = tcp->dest;
}
} else if (key->ip.proto == IPPROTO_UDP) {
key_len = SW_FLOW_KEY_OFFSET(ipv4.tp);
if (udphdr_ok(skb)) {
struct udphdr *udp = udp_hdr(skb);
key->ipv4.tp.src = udp->source;
key->ipv4.tp.dst = udp->dest;
}
} else if (key->ip.proto == IPPROTO_ICMP) {
key_len = SW_FLOW_KEY_OFFSET(ipv4.tp);
if (icmphdr_ok(skb)) {
struct icmphdr *icmp = icmp_hdr(skb);
// ICMP type和code利用传输层16bit的端口号域,需要存网络字节序;
key->ipv4.tp.src = htons(icmp->type);
key->ipv4.tp.dst = htons(icmp->code);
}
}
} else if ((key->eth.type == htons(ETH_P_ARP) || key->eth.type == htons(ETH_P_RARP)) && arphdr_ok(skb)) {
struct arp_eth_header *arp;
arp = (struct arp_eth_header *)skb_network_header(skb);
if (arp->ar_hrd == htons(ARPHRD_ETHER)
&& arp->ar_pro == htons(ETH_P_IP)
&& arp->ar_hln == ETH_ALEN
&& arp->ar_pln == 4) {
/* We only match on the lower 8 bits of the opcode. */
if (ntohs(arp->ar_op) <= 0xff)
key->ip.proto = ntohs(arp->ar_op);
memcpy(&key->ipv4.addr.src, arp->ar_sip, sizeof(key->ipv4.addr.src));
memcpy(&key->ipv4.addr.dst, arp->ar_tip, sizeof(key->ipv4.addr.dst));
memcpy(key->ipv4.arp.sha, arp->ar_sha, ETH_ALEN);
memcpy(key->ipv4.arp.tha, arp->ar_tha, ETH_ALEN);
key_len = SW_FLOW_KEY_OFFSET(ipv4.arp);
}
} else if (key->eth.type == htons(ETH_P_IPV6)) {
int nh_len; /* IPv6 Header + Extensions */
nh_len = parse_ipv6hdr(skb, key, &key_len);
if (unlikely(nh_len < 0)) {
if (nh_len == -EINVAL)
skb->transport_header = skb->network_header;
else
error = nh_len;
goto out;
}
if (key->ip.frag == OVS_FRAG_TYPE_LATER)
goto out;
if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP)
key->ip.frag = OVS_FRAG_TYPE_FIRST;
/* Transport layer. */
if (key->ip.proto == NEXTHDR_TCP) {
key_len = SW_FLOW_KEY_OFFSET(ipv6.tp);
if (tcphdr_ok(skb)) {
struct tcphdr *tcp = tcp_hdr(skb);
key->ipv6.tp.src = tcp->source;
key->ipv6.tp.dst = tcp->dest;
}
} else if (key->ip.proto == NEXTHDR_UDP) {
key_len = SW_FLOW_KEY_OFFSET(ipv6.tp);
if (udphdr_ok(skb)) {
struct udphdr *udp = udp_hdr(skb);
key->ipv6.tp.src = udp->source;
key->ipv6.tp.dst = udp->dest;
}
} else if (key->ip.proto == NEXTHDR_ICMP) {
key_len = SW_FLOW_KEY_OFFSET(ipv6.tp);
if (icmp6hdr_ok(skb)) {
error = parse_icmpv6(skb, key, &key_len, nh_len);
if (error < 0)
goto out;
}
}
}
out:
*key_lenp = key_len;
return error;
}
A1. struct iphdr结构体解析:
00169 struct iphdr {
00170 #if defined(__LITTLE_ENDIAN_BITFIELD)
00171 __u8 ihl:4,
00172 version:4;
00173 #elif defined (__BIG_ENDIAN_BITFIELD)
00174 __u8 version:4,
00175 ihl:4;
00176 #else
00177 #error "Please fix "
00178 #endif00179 __u8 tos;
00180 __u16 tot_len;
00181 __u16 id;
00182 __u16 frag_off;
00183 __u8 ttl;
00184 __u8 protocol;
00185 __u16 check;
00186 __u32 saddr;
00187 __u32 daddr;
00188 /*The options start here. */
00189 };
iphdr->version 版本(4位)。iphdr->ihl 首部长度(4位),单位是4B,包括任何选项,首部最长为60个字节。普通IP数据报(没有任何选择项时)该字段的值是5(所以通常IP头20B)。iphdr->tos 服务类型字段(8位): 该字段包括一个3 bit的优先权子字段(现在已被忽略),4 bit的TOS子字段和1 bit未用位但必须置0。4 bit的TOS子字段分别代表:最小时延、最大吞吐量、最高可靠性和最小费用。4 bit中只能设置其中1 bit。如果所有4 bit均为0,那么就意味着是一般服务。iphdr->tot_len 总长度字段(16位)是指整个IP数据报的长度,以字节为单位。利用首部长度字段和总长度字段,就可以知道 IP数据报中数据内容的起始位置和长度。由于该字段长16比特,所以IP数据报最长可达65535字节, 总长度字段是IP首部中必要的内容,因为一些数据链路(如以太网)需要填充一些数据以达到最小长度。尽管以太网的最小帧长为46字节,但是IP数据可能会更短。如果没有总长度字段,那么IP层就不知道46字节中有多少是IP数据报的内容。iphdr->id 标识字段(16位)唯一地标识主机发送的每一份数据报(send seq)发送一份报文它的值就会加1。iphdr->frag_off (16位) frag_off域的低13位 -- 片偏移(Fragment offset)域指明了该分段在当前数据报中的什么位置上。除了一个数据报的最后一个分段以外,其他所有的分段(分片)必须是8字节的倍数。这是8字节是基本分段单位。由于该域有13个位,所以,每个数据报最多有8192个分段。因此,最大的数据报长度为65,536字节,比iphdr->tot_len域还要大1。iphdr->frag_off的高3位 (1) 比特0是保留的,必须为0; (2) 比特1是“更多分片”(MF -- More Fragment)标志。除了最后一片外,其他每个组成数据报的片都要把该比特置1。 (3) 比特2是“不分片”(DF -- Don't Fragment)标志,如果将这一比特置1,IP将不对数据报进行分片,这时如果有需要进行分片的数据报到来,会丢弃此数据报并发送一个ICMP差错报文给起始端。 iphdr->ttl TTL(8位) 生存时间字段设置了数据报可以经过的最多路由器数。它指定了数据报的生存时间。TTL的初始值由源主机设置(通常为32或64),一旦经过一个处理它的路由器,它的值就减去1。当该字段的值为0时,数据报就被丢弃,并发送ICMP报文通知源主机。
iphdr->protocol 协议字段(8位):标识上层协议类型。 当网络层组装完成一个完整的数据报之后,它需要知道主机该如何对它进行处理。协议域指明了该将它交给哪个传输进程。
iphdr->check 首部检验和字段(16位),是根据IP首部计算的检验和码。它不对首部后面的数据进行计算。 而ICMP、IGMP、UDP和TCP的首部校验和字段涉及首部和数据。iphdr->saddr , iphdr->daddr 32位源目IP地址