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

相关内容

热门资讯

蜻蜓订购APP贵金属订购非法期...   蜻蜓订购APP以"投资平台"名义对外宣传,主打贵金属期货(订购)交易。该平台一方面强调自身"正规...
悟空订购APP非法期货交易欺骗...   近期,有投资者反映,“悟空订购APP”等平台以白银、贵金属订购为名,宣传所谓“高收益”“低门槛”...
小赢淘金APP期货投资骗局,平...   “小赢淘金”之流,最擅长的就是用“注册送现金”、“充值返券”的糖衣炮弹来引流。别天真地以为那是福...
八方淘金APP非法期货交易欺骗...   警惕!八方生财、每日淘金、小赢淘金、悟空订购、蜻蜓订购、斗金订购等APP均涉嫌非法期货交易!  ...
智易购APP贵金属订购交易骗局...   很多人看到“3元起投”“充值送福利”“操作简单”“随时提现”这类宣传,很容易把智易购APP当成普...
喜玛银庄APP商品订购投资骗局...   喜玛银庄APP白银、铂金“现货订购”骗局警示:无资质平台涉嫌非法期货交易,投资者爆仓后如何维权?...
吉鑫乐享APP炒贵金属订购交易...   近期,多名投资者反映在“吉鑫乐享APP”进行贵金属投资时遭遇严重亏损,甚至血本无归。看似正规的“...
易掘金APP虚假期货投资骗局,...   “易掘金”的APP它打着“高收益、灵活投资”的旗号,宣称通过“商品订购”就能轻松赚钱,吸引了不少...
在大吉订购APP订购交易亏损被...   近期,不少投资者反映在“大吉订购App”上投资白银、铂金等商品遭遇严重亏损。该平台宣称提供现货订...
铂银壹号APP商品订购交易亏损...   许多中小投资者误以为网上的“商品现货订购”是合规合法的理财渠道,殊不知这背后隐藏着巨大的合规风险...
兴鑫淘金APP贵金属订购交易亏...   白银、贵金属或其他商品类投资,除国家批准的正规期货交易场所及持牌期货公司外,市面上大量第三方交易...
铂银掘金APP无资质的违规平台...   铂银掘金等平台的投资者,往往是被“注册送现金”“充值返现券”“首单免亏”等广告话术吸引,进而下载...
八方淘金APP订购交易不合规,...   “八方淘金APP”“铂银掘金APP”“铂银壹号APP”等软件被指以“现货订购”为名,实际采用高杠...
在指尖订购APP订购商品亏损被...   指尖订购APP这类平台,表面是“订购交易”,实则是披着电商外衣的非法期货陷阱。绝大多数投资者被蒙...
在犀贝易购APP做商品订购交易...   市面上打着“现货投资”“贵金属交易”旗号的APP并不少,但真正合规、受监管的平台并不多。犀贝易购...
易达淘金APP虚假商品订购交易...   易达淘金APP存在严重的虚假宣传与违规交易行为。该平台打着“商品订购”的旗号,实则通过不合规的交...
大浪淘金APP白银订购亏损被骗...   大浪淘金APP最隐蔽的套路,在于其完全封闭的内盘交易系统。表面上,它宣称与国际贵金属市场接轨;实...
一品好购APP上订购白银出现亏...   一品好购APP原名“一品订购”,运营方为上海宝智未来信息技术有限公司。近期不少投资者反映在该平台...
九州订购APP贵金属订购投资骗...   九州订购APP正通过抖.音、快手、今日头条等平台大量投放投资广告,用“5元起投”“充值返现”“0...
坚锅淘金APP交易软件并不正规...   当前,市面上大量所谓的“现货商城订购”交易软件,早已偏离了正常的实物商品买卖,演变为高杠杆、高手...