Linux网桥中的伪造路由

内核中在新建一个网桥时,为其分配一个伪造的路由表项(fake_rtable)。之所以是伪造,因为并不是通过查询FIB路由表而得出,而是手动创建并且不能用于路由数据包,其操作函数集中大部分函数为空,目前仅fake_mtu函数会返回一个相关网络设备device的mtu值,其它函数诸如fake_redirect等都为空。


static unsigned int fake_mtu(const struct dst_entry *dst)
{
    return dst->dev->mtu;
}
static struct dst_ops fake_dst_ops = {
    .update_pmtu    = fake_update_pmtu,
    .redirect   = fake_redirect,
    .cow_metrics    = fake_cow_metrics,
    .neigh_lookup   = fake_neigh_lookup,
    .mtu        = fake_mtu,
};


伪路由初始化


网桥的伪路由由函数br_netfilter_rtable_init初始化,可以看到只是初始化了路由dst的MTU为1500(PMTU)。代码中注释对伪造路由做了如下解释:

/*

 * Initialize bogus route table used to keep netfilter happy.
 * Currently, we fill in the PMTU entry because netfilter
 * refragmentation needs it, and the rt_flags entry because
 * ipt_REJECT needs it.  Future netfilter modules might
 * require us to fill additional fields.
 */
/*
 * 伪造路由表项可使得netfilter模块正常工作。
 * 当前,由于netfilter的重新分片功能需要PMTU,ipt_REJECT操作需要使用rt_flags变量,所以只初始化了此两者。
 * 未来如果netfilter模块需要使用到路由表项的其它字段再行增加。
 */
static const u32 br_dst_default_metrics[RTAX_MAX] = {
    [RTAX_MTU - 1] = 1500,
};


有以下代码可见,路由rtable的成员rt_flags变量没有进行显示的初始化,即rt_flags等于0。

void br_netfilter_rtable_init(struct net_bridge *br)
{
    struct rtable *rt = &br->fake_rtable;

    atomic_set(&rt->dst.__refcnt, 1);
    rt->dst.dev = br->dev;
    rt->dst.path = &rt->dst;
    dst_init_metrics(&rt->dst, br_dst_default_metrics, true);
    rt->dst.flags   = DST_NOXFRM | DST_FAKE_RTABLE;
    rt->dst.ops = &fake_dst_ops;
}


伪路由的使用


首先来看一下rt_flags的使用,具体在文件net/ipv4/netfilter/ipt_REJECT.c中。也就是iptables的reject操作,由此可见,rt_flags需要在打开了网桥调用iptables功能之后才会有效。比如在网桥模式下,配置拒绝源IP地址在192.168.1.0/24网段的数据包,规则如下:

iptables -A INPUT -p ALL -s 192.168.1.0/24  -j REJECT --reject-with icmp-host-prohibited

匹配此规则的数据包,将会被丢弃,并且回复一个icmp-host-prohibited数据包。netfilter使用nf_send_unreach函数发送此数据包,其最终调用icmp_send处理。此函数中需要使用我们之前初始化的rt_flags的值,具体如下代码:


void icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info)
{
    if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
        goto out;
}


再来看初始化了的dst的PMTU的使用。同样在ipt_REJECT.c文件中,如果配置了以下的iptables规则,在匹配到源地址为192.168.1.0/24网段的数据包后,系统发送TCP reset报文给源端:

sudo iptables -A INPUT -p tcp  -s 192.168.1.0/24  -j REJECT --reject-with tcp-rst

内核调用函数nf_send_reset实现。其需要访问dst的pmtu验证新创建的nskb的长度是否合法。ip4_dst_hoplimit会访问到dst metric的RTAX_HOPLIMIT字段,此字段并未做初始化,其值为零,ip4_dst_hoplimit会采用系统默认的hoplimit(sysctl_ip_default_ttl)。


void nf_send_reset(struct sk_buff *oldskb, int hook)
{
    skb_dst_set_noref(nskb, skb_dst(oldskb));

    niph = nf_reject_iphdr_put(nskb, oldskb, IPPROTO_TCP,
                   ip4_dst_hoplimit(skb_dst(nskb)));

    if (nskb->len > dst_mtu(skb_dst(nskb)))
        goto free_nskb;
}


另外,在netfilter调用br_nf_ip_fragment发送数据包时,需要使用dst的mtu判断是否要进行分片。


static int br_nf_ip_fragment(struct net *net, struct sock *sk, struct sk_buff *skb, ...)
{
    unsigned int mtu = ip_skb_dst_mtu(skb);
    struct iphdr *iph = ip_hdr(skb);

    if (unlikely(((iph->frag_off & htons(IP_DF)) && !skb->ignore_df) ||
             (IPCB(skb)->frag_max_size &&
              IPCB(skb)->frag_max_size > mtu))) {
        IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS);
        kfree_skb(skb);
        return -EMSGSIZE;
    }
    return ip_do_fragment(sk, skb, output);
}


内核版本

linux-3.10.0


猜你喜欢

转载自blog.csdn.net/sinat_20184565/article/details/80753539
今日推荐