博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Netfilter框架
阅读量:4138 次
发布时间:2019-05-25

本文共 5895 字,大约阅读时间需要 19 分钟。

1. Netfilter/IPTables简介

    Netfilter/IPTables是继2.0.x的ipfwadm、2.2.x的ipchains之后,新一代的 Linux防火墙机制。Netfilter采用模块化设计,具有良好的可扩充性。其重要工具模块IPTables连接到 NetFilter的架构中,并允许使用者对数据报进行过滤、地址转换、处理等操作.Netfilter提供了一个框架,将对网络代码的直接干涉降到最低,并允许用规定的借口将其他包处理代码以模块的形式添加到内核中,具有极强的灵活性,当然自从2.6内核后,netfilter也把NAT加了进来.

   开篇先来了一段专业叙述^^,其实本人接触netfilter也时日不多,一直想把整个框架流程给搞明白,做网络性能优化的时候接触了点,后来又实现8021p也用到了些(主要和过滤策略结合起来).这里就3.1.1的内核做了个分析.下面先看下源码树:

 

 源码目录:kernel/net/ipv4/netfilter/*

           Kernel/net/ipv6/netfilter/*

           Kernel/net/netfilter/*

2.netfilter功能框架

     netfilter主要采用连接跟踪(Connection Tracking)、包过滤(Packet Filtering)、地址转换(NAT)、包处理(Packet Mangling)4种关键技术.

连接追踪

  连接跟踪是包过滤、地址转换的基础,它作为一个独立的模块运行。采用连接跟踪技术在协议栈低层截取数据包,将当前数据包及其状态信息与历史数据包及其状态信息进行比较,从而得到当前数据包的控制信息,根据这些信息决定对网络数据包的操作,达到保护网络的目的.

      当下层网络接收到初始化连接同步(SynchronizeSYN)包,将被netfilter规则库检查。该数据包将在规则链中依次序进行比较。如果该包应被丢弃,发送一个复位(ResetRST)包到远端主机,否则连接接收。这次连接的信息将被保存在连接跟踪信息表中,并表明该数据包所应有的状态。这个连接跟踪信息表位于内核模式下,其后的网络包就将与此连接跟踪信息表中的内容进行比较,根据信息表中的信息来决定该数据包的操作。因为数据包首先是与连接跟踪信息表进行比较,只有SYN包才与规则库进行比较,数据包与连接跟踪信息表的比较都是在内核模式下进行的,所以速度很快.

包过滤

      包过滤检查通过的每个数据包的头部,然后决定如何处置它们,可以选择丢弃,让包通过,或者更复杂的操作.

地址转换

      网络地址转换源(NAT)分为(Source NATSNAT)和目的NAT(Destination NAT, DNAT)2种不同的类型。SNAT是指修改数据包的源地址(改变连接的源IP)SNAT会在数据包送出之前的最后一刻做好转换工作。地址伪装(Masquerading)SNAT的一种特殊形式。DNAT 是指修改数据包的目标地址(改变连接的目的IP)DNAT 总是在数据包进入以后立即完成转换。端口转发、负载均衡和透明代理都属于DNAT.

包处理

     利用包处理可以设置或改变数据包的服务类型(Type of Service, TOS)字段;改变包的生存期(Time to Live, TTL)字段;在包中设置标志值,利用该标志值可以进行带宽限制和分类查询.

 

通过上面的概述我们已经对netfilter已经有了个大致的概念和模块的划分.那面我们还是看一下它的框架图吧:

 

这个图包括了应该所以的hook的地方,具体有IP层的5个,bridge的5个,还有arp层的3个. 这个图只是标出钩子点.没有具体的函数调用.(谅解^^).下面我们就分析下这个框架是如何运作起来的.

    通过上一篇NAPI机制我们知道,最后数据包通过netif_receive_skb这个函数来查找具体的以太网协议处理函数继续往上走.下面我们就来看下这个函数:

 

    /**
 * netif_receive_skb - process receive buffer from network
 * @skb: buffer to process
 *
 * netif_receive_skb() is the main receive data processing function.
 * It always succeeds. The buffer may be dropped during processing
 * for congestion control or by the protocol layers.
 *
 * This function may only be called from softirq context and interrupts
 * should be enabled.
 *
 * Return values (usually ignored):
 * NET_RX_SUCCESS: no congestion
 * NET_RX_DROP: packet was dropped
 */
int netif_receive_skb(struct sk_buff *skb)
{


 if (netdev_tstamp_prequeue)
  net_timestamp_check(skb);

 if (skb_defer_rx_timestamp(skb))
  return NET_RX_SUCCESS;

#ifdef CONFIG_RPS
 {


  struct rps_dev_flow voidflow, *rflow = &voidflow;
  int cpu, ret;

  rcu_read_lock();

  cpu = get_rps_cpu(skb->dev, skb, &rflow);

  if (cpu >= 0) {


   ret = enqueue_to_backlog(skb, cpu, &rflow->last_qtail);
   rcu_read_unlock();
  } else {

   rcu_read_unlock();
   ret = __netif_receive_skb(skb);
  }

  return ret;
 }
#else
 return __netif_receive_skb(skb);
#endif
}

这里我们发现具体的处理数据的代码已经被封装了. 包括我们看3.1.1的process_backlog代码就会清楚,它是直接调用了__netif_receive_skb这个函数:


 

点击(此处)折叠或打开

  1. static int __netif_receive_skb(struct sk_buff *skb)
  2. {

  3.     struct packet_type *ptype, *pt_prev;
  4.     rx_handler_func_t *rx_handler;
  5.     struct net_device *orig_dev;
  6.     struct net_device *null_or_dev;
  7.     bool deliver_exact = false;
  8.     int ret = NET_RX_DROP;
  9.     __be16 type;

  10.     if (!netdev_tstamp_prequeue)
  11.         net_timestamp_check(skb);

  12.     trace_netif_receive_skb(skb);

  13.     /* if we've gotten here through NAPI, check netpoll */
  14.     if (netpoll_receive_skb(skb))
  15.         return NET_RX_DROP;

  16.     if (!skb->skb_iif)
  17.         skb->skb_iif = skb->dev->ifindex;
  18.     orig_dev = skb->dev;

  19.     skb_reset_network_header(skb);
  20.     skb_reset_transport_header(skb);
  21.     skb_reset_mac_len(skb);

  22.     pt_prev = NULL;

  23.     rcu_read_lock();

  24. another_round:

  25.     __this_cpu_inc(softnet_data.processed);

  26.     if (skb->protocol == cpu_to_be16(ETH_P_8021Q)) {

  27.         skb = vlan_untag(skb);
  28.         if (unlikely(!skb))
  29.             goto out;
  30.     }

  31. #ifdef CONFIG_NET_CLS_ACT
  32.     if (skb->tc_verd & TC_NCLS) {

  33.         skb->tc_verd = CLR_TC_NCLS(skb->tc_verd);
  34.         goto ncls;
  35.     }
  36. #endif

  37.     list_for_each_entry_rcu(ptype, &ptype_all, list) {

  38.         if (!ptype->dev || ptype->dev == skb->dev) {

  39.             if (pt_prev)
  40.                 ret = deliver_skb(skb, pt_prev, orig_dev);
  41.             pt_prev = ptype;
  42.         }
  43.     }

  44. #ifdef CONFIG_NET_CLS_ACT
  45.     skb = handle_ing(skb, &pt_prev, &ret, orig_dev);
  46.     if (!skb)
  47.         goto out;
  48. ncls:
  49. #endif

  50.     rx_handler = rcu_dereference(skb->dev->rx_handler);
  51.     if (rx_handler) {

  52.         if (pt_prev) {

  53.             ret = deliver_skb(skb, pt_prev, orig_dev);
  54.             pt_prev = NULL;
  55.         }
  56.         switch (rx_handler(&skb)) {

  57.         case RX_HANDLER_CONSUMED:
  58.             goto out;
  59.         case RX_HANDLER_ANOTHER:
  60.             goto another_round;
  61.         case RX_HANDLER_EXACT:
  62.             deliver_exact = true;
  63.         case RX_HANDLER_PASS:
  64.             break;
  65.         default:
  66.             BUG();
  67.         }
  68.     }

  69.     if (vlan_tx_tag_present(skb)) {

  70.         if (pt_prev) {

  71.             ret = deliver_skb(skb, pt_prev, orig_dev);
  72.             pt_prev = NULL;
  73.         }
  74.         if (vlan_do_receive(&skb)) {

  75.             ret = __netif_receive_skb(skb);
  76.             goto out;
  77.         } else if (unlikely(!skb))
  78.             goto out;
  79.     }

  80.     /* deliver only exact match when indicated */
  81.     null_or_dev = deliver_exact ? skb->dev : NULL;

  82.     type = skb->protocol;
  83.     list_for_each_entry_rcu(ptype,
  84.             &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {

  85.         if (ptype->type == type &&
  86.          (ptype->dev == null_or_dev || ptype->dev == skb->dev ||
  87.          ptype->dev == orig_dev)) {

  88.             if (pt_prev)
  89.                 ret = deliver_skb(skb, pt_prev, orig_dev);
  90.             pt_prev = ptype;
  91.         }
  92.     }

  93.     if (pt_prev) {

  94.         ret = pt_prev->func(skb, skb->dev, pt_prev, orig_dev);
  95.     } else {

  96.         atomic_long_inc(&skb->dev->rx_dropped);
  97.         kfree_skb(skb);
  98.         /* Jamal, now you will not able to escape explaining
  99.          * me how you were going to use this. :-)
  100.          */
  101.         ret = NET_RX_DROP;
  102.     }

  103. out:
  104.     rcu_read_unlock();
  105.     return ret;
  106. }

这里我们不关注其他代码,只关注黑体标注的部分.首先是第一个list_for_each_entry_rcu部分,它查询ptype_all链表注册的协议处理函数,这里匹配的是ETH_P_ALL(具体定义在if_ether.h头文件里),注册是通过dev_add_pack这个函数,这个链表默认为空. 第二个list_for_each_entry_rcu就是具体的查询具体协议的处理函数里,比如ip_rcv . 它查询的是ptype_base链表,注册函数依然是dev_add_pack函数.这里我们贴出部分关键代码:

 type = skb->protocol;
 list_for_each_entry_rcu(ptype,
   &ptype_base[ntohs(type) & PTYPE_HASH_MASK], list) {


  if (ptype->type == type &&

(ptype->dev == null_or_dev || ptype->dev == skb->dev ||
       ptype->dev == orig_dev)) {


   if (pt_prev)
    ret = deliver_skb(skb, pt_prev, orig_dev);
   pt_prev = ptype;
  }
 }

   .....

通过deliver_skb就调用到了具体的协议接受模块了.但是我们发现在__netif_receive_skb里没有handle_bridge函数,那么哪里来处理bridge的包呢? 后来发现在两个list_for_each_entry_rcu查询之间有这么一行:

rx_handler = rcu_dereference(
skb->dev->rx_handler

转载地址:http://gnhvi.baihongyu.com/

你可能感兴趣的文章
C 语言 学习---获取文本框内容及字符串拼接
查看>>
C 语言学习 --设置文本框内容及进制转换
查看>>
C 语言 学习---判断文本框取得的数是否是整数
查看>>
C 语言 学习---ComboBox相关、简单计算器
查看>>
C 语言 学习---ComboBox相关、简易“假”管理系统
查看>>
C 语言 学习---回调、时间定时更新程序
查看>>
C 语言 学习---复选框及列表框的使用
查看>>
第十一章 - 直接内存
查看>>
JDBC核心技术 - 上篇
查看>>
一篇搞懂Java反射机制
查看>>
Single Number II --出现一次的数(重)
查看>>
Palindrome Partitioning --回文切割 深搜(重重)
查看>>
对话周鸿袆:从程序员创业谈起
查看>>
Mysql中下划线问题
查看>>
Xcode 11 报错,提示libstdc++.6 缺失,解决方案
查看>>
idea的安装以及简单使用
查看>>
Windows mysql 安装
查看>>
python循环语句与C语言的区别
查看>>
vue 项目中图片选择路径位置static 或 assets区别
查看>>
vue项目打包后无法运行报错空白页面
查看>>