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

相关内容

热门资讯

消息称百度旗下昆仑芯瞄准500... 6 月 29 日消息,据《The Information》昨日援引知情人士消息,百度旗下 AI 芯片...
打造夏日消费新场景 第35届北... 北京商报讯(记者 翟枫瑞)6月29日消息,第35届北京国际燕京啤酒文化节新闻发布会在京举行。本届啤酒...
社保基金持仓数据出炉,一季度增... 最近各大上市公司一季度财报都公开了,咱们国家社保基金的持仓数据也全部曝光。目前社保拿着比亚迪价值44...
36氪首发 | 海思、中兴团队... 作者 | 乔钰杰 编辑 | 袁斯来 硬氪获悉,广州宸思通讯科技有限公司(以下简称“宸思科技”)近日完...
两天蒸发47亿市值!一纸税务通... 一纸税务通知书,能让一家百亿龙头两天蒸发47亿市值。 6月22日,北大荒(600598.SH)公告称...
SK海力士将投资1100万亿韩... SK集团会长崔泰源6月29日在韩国“三大重大计划”发布会上宣布,公司将投资1100万亿韩元扩大半导体...
两只A股,终止上市! 两家A股公司,即将摘牌。 6月29日,退市沪科(600608.SH)公告称,上海证券交易所将在202...
原创 M... 一家成立近十年的自动驾驶公司,在IPO时吸引了14家基石投资者认购近一半的发行股份,其中不乏奔驰、比...
基金忠言|国寿安保滤镜碎,三年... 图片来源:视觉中国 蓝鲸新闻6月29日讯(记者 祁和忠)保险系基金公司国寿安保总经理换人了。 6月2...
三星电机计划加码玻璃基板!相关... 6月29日,玻璃基板概念股午后有所回升, 华工科技(000988.SZ)逼近涨停, 彩虹股份(600...
拉萨海关持续壮大外贸经营主体 ...   新华网拉萨6月28日电(记者蒋梦辰)近日,记者从拉萨海关获悉,今年前5个月,西藏有进出口实绩的外...
机构:二季报临近,医药生物板块... 6月29日,华源证券发布了一篇医药生物行业的研究报告,报告指出,业绩期临近,产业链景气度有望再次迎来...
每日收评科创50放量涨超4.5... 财联社6月29日讯,三大指数全线收红,创业板指探底回升,科创50指数大涨4.61%。沪深两市成交额3...
6月多地土拍结构性升温:深圳单... 进入2026年6月,不少城市核心区地块集中诞生高溢价宗地,热度突出的城市包含深圳、杭州、长沙。 其中...
业绩炸裂!盛达资源半年预盈3.... 6月29日,贵金属矿山龙头盛达资源(000603.SZ)发布 2026 年半年度业绩预告,上半年业绩...
A股午后拉升三大股指收涨:半导... A股三大股指6月29日开盘涨跌互现。早盘沪强深弱,创指一度跌超2%。半导体午后拉升,带动两市上涨,沪...
原创 空... 前言 大家好,我是老金。 这几天,两幅极度割裂的画面放在一起,把我看笑了。 一边是在持续的热浪下,欧...
澳大利亚审慎监管局拟放宽银行风... 澳大利亚审慎监管局(APRA)6月29日就修改 银行信用风险资本设定公开征求意见,旨在加大信贷投放以...
全民炒股,急踩刹车!韩国股市突... 屈红燕/证券时报网 全民狂欢、交易高度拥挤、杠杆资金猛增、新入市投资者表现激进、大型IPO吸金等现象...