如何由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地址

相关内容

热门资讯

阿联酋最大银行及另两家中东银行... 观点网讯:5月8日,路透社报道指,阿联酋最大银行第一阿布扎比银行(First Abu Dhabi B...
深圳239亿地王易主,再造万象... 2017年,世茂集团豪掷239.43亿元拿下世茂深港国际中心地块,曾规划建筑高度达700米的深圳第一...
蔚来在安庆成立新能源科技公司 ... 天眼查App显示,近日,安庆蔚来新能源科技有限公司成立,法定代表人为姚蒀,注册资本500万人民币,经...
美国牛肉商期盼峰会重启对华出口 据路透社5月8日报道,美国牛肉生产商正期待特朗普与中国于5月14日至15日的峰会推动对华出口许可恢复...
创业板首家未盈利企业,市值突破... 5月8日,大普微总市值正式突破2000亿元大关。截至午间收盘,大普微涨14.07%,报493.1元/...
招商证券:董事长霍达因工作变动... 招商证券公告,公司董事长霍达因工作变动申请辞去董事长、执行董事等全部职务,辞任自辞呈送达董事会之日生...
原创 中... 【阅读须知】本文所引用的所有信息和数据,均为作者通过查阅官方资料与网络公开数据整理、分析而成,旨在为...
原创 从... 2026年5月5日,中国商务部发布了一项具有划时代意义的专项阻断禁令,这份公告让一向倚仗长臂管辖的美...
布米普特拉北京投资基金管理有限... 美国圣路易斯联邦储备银行总裁穆萨莱姆周三发出明确信号,美联储货币政策面临的潜在风险正在发生关键转向。...
加工的秘密:超精加工设备如何做... 你知道吗? 一根头发丝的直径大约0.07毫米,也就是70微米。 超精加工设备,可切出表面,其尺寸为0...
招商证券董事长霍达因工作变动离... 北京商报讯(记者 刘宇阳 实习生 王思奕)5月8日,招商证券发布关于公司董事长离任暨推举董事代行董事...
华帝股份营收创近3年新低,37... 乐居财经李兰近日,华帝股份(002035.SZ)发布2025年年度报告。 2025年,华帝股份实现营...
大模型融资杀疯了!月之暗面狂揽... 图源:视觉中国 5月7日,据华峰资本官微消息,国内头部大模型公司月之暗面(Kimi)于近日完成新一轮...
扎根长宁二十余载,仲利国际融资... 作为总部扎根上海长宁的优质台资金融企业,仲利国际融资租赁有限公司深耕融资租赁行业二十余载,始终坚守金...
估值210亿!李彦宏又将收获一... 来源:直通IPO,文/王非 国产GPU上市潮仍然汹涌,继两家登陆A股、两家登陆H股后,这家公司正推进...
基金“盲盒”拆了 公募基金正在迎来一场让投资者“看得懂”的变革。 近日,华夏、易方达、南方、招商等12家头部及特色基金...
原创 2... 前言 十年间,中国企业在印尼镍产业链累计砸下超过140亿美元,电厂、公路、码头和全套生产线,硬生生...
原创 欧... 俄罗斯卫星通讯社5月6日报道,欧盟宣布禁止欧洲银行为含有来自不可靠供应商的关键部件的可再生能源项目提...
原创 余... 2026年5月2日,在中国理财市场扎根十三年的余额宝,终于触碰到了一个让所有人错愕的数字——7日年化...
银华基金增聘谭普景共同管理银华... 来源:新浪基金∞工作室 5月8日,银华基金管理股份有限公司发布公告称,为银华中证机器人交易型开放式指...