Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4513332
  • 博文数量: 252
  • 博客积分: 5347
  • 博客等级: 大校
  • 技术积分: 13838
  • 用 户 组: 普通用户
  • 注册时间: 2009-09-30 10:13
文章分类
文章存档

2022年(12)

2017年(11)

2016年(7)

2015年(14)

2014年(20)

2012年(9)

2011年(20)

2010年(153)

2009年(6)

分类: LINUX

2014-07-13 14:55:17

    当上联路由设备配置为桥模式时,下挂的PC通过PPPOE拨号连接互联网时,这时PPPOE发送的数据包是添加了PPPOE封装的数据包,当经过路由的桥模式进行透传时,在桥的转发处理流程中对PPPOE封装的数据包进行了特殊的处理,这里的Linux内核版本为Linux2.6.32

         首先,需要先了解一下数据包在桥模式下的转发流程,在桥的br_nf_pre_routing函数中对PPPOEVLAN的处理如下:

点击(此处)折叠或打开

  1. if (skb->protocol != htons(ETH_P_IP) && !IS_VLAN_IP(skb) &&
  2.              !IS_PPPOE_IP(skb))
  3.          {
  4.                    return NF_ACCEPT;
  5.          }

         这里对数据包进行了判断,这里重点看一下对VLANPPPOE封装的处理

点击(此处)折叠或打开

  1. static inline __be16 vlan_proto(const struct sk_buff *skb)
  2. {
  3.          return vlan_eth_hdr(skb)->h_vlan_encapsulated_proto;
  4. }
  5. #define IS_VLAN_IP(skb) \
  6.          (skb->protocol == htons(ETH_P_8021Q) && \
  7.           vlan_proto(skb) == htons(ETH_P_IP) && \
  8.           brnf_filter_vlan_tagged)
  9.  
  10. static inline __be16 pppoe_proto(const struct sk_buff *skb)
  11. {
  12.          return *((__be16 *)(skb_mac_header(skb) + ETH_HLEN +
  13.                                 sizeof(struct pppoe_hdr)));
  14. }
  15.  
  16. #define IS_PPPOE_IP(skb) \
  17.          (skb->protocol == htons(ETH_P_PPP_SES) && \
  18.           pppoe_proto(skb) == htons(PPP_IP) && \
  19.           brnf_filter_pppoe_tagged)

这里就是判断是不是带VLAN的数据包 PPPOE封装的数据报文,但在IS_PPPOE_IP函数中有一个变量brnf_filter_pppoe_tagged,这Linux系统中默认的值为0.

点击(此处)折叠或打开

  1. #ifdef CONFIG_SYSCTL
  2. static struct ctl_table_header *brnf_sysctl_header;
  3. static int brnf_call_iptables __read_mostly = 1;
  4. static int brnf_call_ip6tables __read_mostly = 1;
  5. static int brnf_call_arptables __read_mostly = 1;
  6. static int brnf_filter_vlan_tagged __read_mostly = 0;
  7. static int brnf_filter_pppoe_tagged __read_mostly = 0;
  8. #else
  9. #define brnf_filter_vlan_tagged 0
  10. #define brnf_filter_pppoe_tagged 0
  11. #endif

       所以在br_nf_pre_routing函数中如果过来的是PPPOE封装的数据包,该宏IS_PPPOE_IP的判断关键是看brnf_filter_pppoe_tagged变量的值,我们可以通过在proc系统中修改该值。

/proc/sys/net/bridge # cat bridge-nf-filter-pppoe-tagged

0

可以使用下面的方式修改该值:

echo 1 >/proc/sys/net/bridge/bridge-nf-filter-pppoe-tagged

      如果在br_nf_pre_routing函数中直接return NF_ACCEPT;后,下面的函数nf_bridge_alloc就不会被调用,这样造成了skb->nf_bridge;为空,这样在br_nf_post_routing函数中,下面的判断会直接返回。

if (!nf_bridge)

                   return NF_ACCEPT;

  HOOK点函数NF_HOOK_THRESH(pf, NF_INET_POST_ROUTING, skb, NULL, realoutdev,

                   br_nf_dev_queue_xmit, NF_IP_PRI_NAT_SRC + 1);

无法调用,导致了NF_INET_POST_ROUTING挂载的函数都无法执行,也就是在__nf_conntrack_confirm函数中做的相应处理对于桥发包都是不生效的。
        当bridge-nf-filter-pppoe-tagged值为真时,还有另外一种情况也会导致通过桥的数据包不会建立会话,当桥下面的PC使用PPPOE拨号上网时,这里假设数据包是带着VLAN的PPPOE数据包,带的VLAN tag为200,在桥的处理函数中br_nf_pre_routing(),我们再看一下上面提到的对PPPOE数据包的判断,如果skb->protocol不等于IP协议,也不是VLAN0x8100,也不是PPPOE0x8864,在这里就会直接返回NF_ACCEPT接收该数据包, 

点击(此处)折叠或打开

  1. if (skb->protocol != htons(ETH_P_IP) && !IS_VLAN_IP(skb) &&
  2.              !IS_PPPOE_IP(skb))
  3.          {
  4.                    /*TODO*/
  5.                    return NF_ACCEPT;
  6.          }

导致下面的两个函数不会被调用

点击(此处)折叠或打开

  1. if (!nf_bridge_alloc(skb))
  2.                    return NF_DROP;
  3.  if (!setup_pre_routing(skb))
  4.                    return NF_DROP;

      所以skb->nf_bridgeNULL,在后续的函数中对skb->nf_bridge进行判断时,都是不成立的。导致刚进入br_nf_post_routing函数,判断nf_bridge为空时,直接返回NF_ACCEPT

if (!nf_bridge)

                   return NF_ACCEPT;

      因此,经过桥上的数据包就不会建立会话, 通过抓包可以看到其发送的报文数据格式:

0000  04 7d 7b 91 0b 69 d8 49 0b b6 8c cb 81 00 00 c8     .}{..i.I........

0010  88 64 11 00 79 08 00 61 00 21 45 00 00 5f be 17     .d..y..a.!E.._..

0020  00 00 3d 11 70 c2 ca 6a 2e 97 72 f9 e2 b9 00 35     ..=.p..j..r....5

0030  ee bb 00 4b ef d3 67 33 81 80 00 01 00 02 00 00     ...K..g3........

         通过上面的打印可以看出,8100VLAN协议,00C8VLAN TAG 200,8864

#define ETH_P_PPP_SES 0x8864              /* PPPoE session messages    */

00 21 #define PPP_IP           0x21          /* Internet Protocol */

         结合pppoe_proto()函数的实现,看看该函数是怎么获取协议的,从MAC层开始+14个字节+6个字节的PPPOE头部信息,应该获取到0x0021,如果没有VLAN TAG4个字节,这样应该是正确,但是如果存在VLAN4个字节,这样就无法取得0x0021的协议字段。而获取到是0x7908。这样就造成了前面提到的if (skb->protocol != htons(ETH_P_IP) && !IS_VLAN_IP(skb) && !IS_PPPOE_IP(skb))的判读为真,直接return NF_ACCEPT。

点击(此处)折叠或打开

  1. static inline __be16 pppoe_proto(const struct sk_buff *skb)
  2. {
  3.          return *((__be16 *)(skb_mac_header(skb) + ETH_HLEN +
  4.                                 sizeof(struct pppoe_hdr)));
  5. }
阅读(4021) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~