如何由skb构造sw_flow_key?
admin
2024-02-06 16:40:41
0

    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地址

相关内容

热门资讯

龙虎榜揭秘!大牛股背后资金动向... 龙虎榜揭秘。 近期A股市场整体波动较为平稳,但不少个股波动剧烈,甚至连续涨停或连续跌停,近日的龙虎榜...
原创 全... 全球都在用美元? 中国偷偷搞了个大动作! 美元占全球支付50%时人民币干了啥? 你可能不知道的是,当...
我国银行理财市场规模突破33万... 银行业理财登记托管中心1月23日发布的《中国银行业理财市场年度报告(2025年)》显示,截至2025...
最高分红率35%!上市银行春节... 随着春节临近,上市银行2025年中期分红逐渐进入尾声。 1月23日,华夏银行、渝农商行迎来2025年...
蔡英丽医生:帕金森患者麻醉注意... 帕金森病是中老年人常见的神经系统退行性疾病,随着病情进展,不少患者可能需要接受各类手术,而麻醉环节的...
原创 利... 朋友们,今天A股发生了一件挺有意思的事:在地面光伏行业不少公司还在为亏损发愁的时候,一个叫做“太空光...
二游王战之局,鹰角先下一城 2026二游王战的启幕来了。1月22日,鹰角《明日方舟:终末地》(以下简称终末地)正式公测,和我们预...
大润发首次跨界合作蛋仔派对,以... 2026年春节前夕,高鑫零售旗下核心品牌大润发与国民级游戏IP《蛋仔派对》正式达成深度跨界合作,共同...
2025年基金4季报重仓股全扫... 随着基金2025年4季报基本披露完毕,记者注意到,截至去年底,基金的重仓股发生了比较明显的变化,有5...
昔日“疫苗之王”科兴控股大消息... 让科兴控股小股东忐忑两个多月的退市事项,迎来新进展。 1月22日晚间,科兴控股生物技术有限公司(以下...
视频丨“片场”成打卡新去处 解... 最近,多个因电影而火热的城市持续收获关注,并积极筹备春节文旅服务。在河南安阳,电影《封神2》对殷商文...
东方新能源汽车主题混合:202... AI基金东方新能源汽车主题混合(400015)披露2025年四季报,第四季度基金利润8586.66万...
数字人民币生态日趋成熟,服贸会... 2025年中国国际服务贸易交易会(以下简称“服贸会”)正在北京举行。作为服贸会九大专题之一,本届金融...
证监会进一步扩大期货市场开放品... 近日,证监会根据《境外交易者和境外经纪机构从事境内特定品种期货交易管理暂行办法》(证监会令第116号...
医保搭台、产业唱戏 多部委协同... 党的二十届四中全会对“十五五”时期扩大高水平对外开放作出了顶层设计和战略擘画,强调了与世界各国共享机...
合众财险2025年揽收保费7.... (图片来源:视觉中国) 蓝鲸新闻1月23日讯(记者 陈晓娟)今日,合众财产保险股份有限公司(下称“合...
仕佳光子:公司高度重视全体股东... 证券日报网讯 1月23日,仕佳光子在互动平台回答投资者提问时表示,公司高度重视全体股东的合法权益,始...
透视行业变革 共话ETF未来—... 1月23日下午,2026年第四届基金生态大会在杭举办。本届大会由同花顺(300033.SZ)与财闻传...
原创 3... 32岁程序员高先生在周六处理工作时突发不适,经抢救无效离世,这起事件引发了关于周末居家加班猝死能否认...