IP層路由適配(IP route)
路由表以及規則組成的系統,可以完成路由的管理以及查找的工作,但是爲了使得IP層的路由工作更加的高效,linux的路由體系裏,route.c裏完成大多數IP層與RPDB的適配工作,以及路由緩衝(route cache)的功能。
調用接口
IP層的路由接口分爲發送路由接口以及接收路由接口:
發送路由接口
IP層在發送數據時如果需要進行路由工作的時候,就會調用ip_route_out函數。這個函數在完成一些鍵值的簡單轉換以後,就會調用ip_route_output_key函數,這個函數首先在緩存裏尋找路由,如果失敗就會調用ip_route_output_slow,ip_route_output_slow裏調用fib_lookup在路由表裏尋找路由,如果命中,首先在緩存裏添加這個路由,然後返回結果。
ip_route_out route.h ip_route_output_key route.c 1984; ip_route_output_slow route.c 1690;" |
接收路由接口
IP層接到一個數據包以後,如果需要進行路由,就調用函數ip_route_input,ip_route_input現在緩存裏尋找,如果失敗則ip_route_inpu調用ip_route_input_slow, ip_route_input_slow裏調用fib_lookup在路由表裏尋找路由,如果命中,首先在緩存裏添加這個路由,然後返回結果。
ip_route_input_slow route.c 1312;" f ip_route_input route.c 1622;" f |
cache
路由緩存保存的是最近使用的路由。當IP在路由表進行路由以後,如果命中就會在路由緩存裏增加該路由。同時系統還會定時檢查路由緩存裏的項目是否失效,如果失效則清除。
/*
* 接收完數據包後的後續處理函數
*/
static int ip_rcv_finish(struct sk_buff *skb)
{
const struct iphdr *iph = ip_hdr(skb);
struct rtable *rt;
/*
* 激活ip_route_input,確定報文的路由,如果ip_route_input無法從FIB中找到路由
* 則丟棄數據報<strong>文,ip_route_input將在IP路由中的專題中進行講解</strong>
*/
<strong><span style="color:#ff0000;">if (skb_dst(skb) == NULL) {</span>
int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos, skb->dev);
if (unlikely(err)) {</strong>
goto drop;
}
}
/*檢查IP報頭裏面是否含有選項,如果含有建立ip_options*/
if (iph->ihl > 5 && ip_rcv_options(skb))
goto drop;
<strong><span style="color:#ff0000;">/*根據dst_entry的結果,使用skb_dst(skb)->input(skb)進行IP的路由選擇
*傳遞給本地計算機的單播或多播,進入 ip_local_deliver();
*單播轉發的報文進入ip_forward()
*多播轉發進入ip_mr_input()
*/</span></strong>
return dst_input(skb);
{
skb_dst(skb)->input(skb)
}
drop:
kfree_skb(skb);
return NET_RX_DROP;
}
#include <linux/ip.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/version.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/dst.h>
#include <net/netfilter/nf_conntrack_acct.h>
MODULE_AUTHOR("xtt");
MODULE_DESCRIPTION("gll");
MODULE_LICENSE("GPL");
MODULE_ALIAS("XTT and GLL");
struct nf_conn_priv {
struct nf_conn_counter ncc[IP_CT_DIR_MAX];
struct dst_entry *dst[IP_CT_DIR_MAX];
};
static unsigned int ipv4_conntrack_getdst (unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
struct nf_conn_counter *acct;
struct nf_conn_priv *dst_info;
ct = nf_ct_get(skb, &ctinfo);
if (!ct || ct == &nf_conntrack_untracked)
return NF_ACCEPT;
acct = nf_conn_acct_find(ct);
if (acct) {
int dir = CTINFO2DIR(ctinfo);
dst_info = (struct nf_conn_priv *)acct;
if (dst_info->dst[dir] == NULL) {
dst_hold(skb_dst(skb));
dst_info->dst[dir] = skb_dst(skb);
}
}
return NF_ACCEPT;
}
static unsigned int ipv4_conntrack_setdst (unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct nf_conn *ct;
enum ip_conntrack_info ctinfo;
struct nf_conn_counter *acct;
struct nf_conn_priv *dst_info;
ct = nf_ct_get(skb, &ctinfo);
if (!ct || ct == &nf_conntrack_untracked)
return NF_ACCEPT;
acct = nf_conn_acct_find(ct);
if (acct) {
int dir = CTINFO2DIR(ctinfo);
dst_info = (struct nf_conn_priv *)acct;
if (dst_info->dst[dir] != NULL) {
// 如果在此設置了skb的dst,那麼在ip_rcv_finish中就不會再去查找路由表了
skb_dst_set(skb, dst_info->dst[dir]);
}
}
return NF_ACCEPT;
}
static struct nf_hook_ops ipv4_conn_dst_info[] __read_mostly = {
{
.hook = ipv4_conntrack_getdst,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_POST_ROUTING,
.priority = NF_IP_PRI_CONNTRACK + 1,
},
{
.hook = ipv4_conntrack_getdst,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_LOCAL_IN,
.priority = NF_IP_PRI_CONNTRACK + 1,
},
{
.hook = ipv4_conntrack_setdst,
.owner = THIS_MODULE,
.pf = NFPROTO_IPV4,
.hooknum = NF_INET_PRE_ROUTING,
.priority = NF_IP_PRI_CONNTRACK + 1,
},
};
static int __init test_info_init(void)
{
int err;
err = nf_register_hooks(ipv4_conn_dst_info, ARRAY_SIZE(ipv4_conn_dst_info));
if (err) {
return err;
}
return err;
}
static void __exit test_info_exit(void)
{
nf_unregister_hooks(ipv4_conn_dst_info, ARRAY_SIZE(ipv4_conn_dst_info));
}
module_init(test_info_init);
module_exit(test_info_exit);