若要使 host1 可直接通过 ip 地址访问 host3,则需在 host2 中配置路由转发。对到来数据包(从 host1 发送到 host3 或反向发送)进行转发的过程被称为 路由选择,是根据被称为路由选择表的数据结构进行。
常见的路由选择:默认网关和默认路由。
在路由选择表中指定默认网关条目时,不与其他路由选择条目匹配的数据包都将转发到默认网关,不管其 IP 报头中的目标地址是什么。
使用无类别域间路由选择表示法时,默认路由用 0.0.0.0/0。作为一个简单案例,可按如下方法将IPV4地址 为 192.168.3.2 的机器指定默认网关。
IP ROUTE ADD DEFAULT VIA 192.168.3.2
// or
ROUTE ADD DEFAULT GATEWAY 192.168.3.2
CIDR(Classless Inter-Domain Routing,无类别域间路由) 表示法是一种用于表示IP地址及其子网掩码的方法。在CIDR表示法中,IP 地址后面跟着一个斜杠(/)和一个数字,这个数字表示 IP 地址中最左侧的连续比特位是网络地址部分(也称为前缀长度)。剩余的位则是主机地址部分。
例如,IP地址 192.168.1.0/24 表示该IP地址的前24位是网络地址部分,后8位是主机地址部分。在这个例子中,子网掩码是 255.255.255.0,因为该子网掩码在二进制表示中有24个连续的1(表示网络部分)和8个连续的0(表示主机部分)。
路由表
网络栈最重要目标是转发流量,对于Internet骨干中的核心路由器来说尤其如此。Linux IP层被称为路由选择子系统,负责转换数据包和维护转发数据包。 接收或发送每个数据包时,都必须在路由选择子系统中查找。
在路由选择子系统中找到匹配条目, FIB_LOOKUP()
将创建一个包含各种路由选择参数 FIB_RESULT
对象,并返回 0
。RTBALE
结构如下:
路由选择子系统的主要数据结构是路由选择表,由结构fib_table
表示。
路由选择条目由结构 fib_info
表示,它包含重要的路由选择条目参数,如出站网络设置、优先级、路由选择协议标识符等。fib_info
对象由方法fib_create_info()
创建,存储在散列表fib_info_hash
中。
缓存:缓存路由选择查找结果是一种优化技术,可用于改善路由选择子系统的性能。
下一跳:结构fib_nh
表示下一跳,包含输出网络设备、外出接口索引等信息。
ICMP重定向消息
有时路由选择条目可能是次优的,在这种情况下将发送 ICMP 重定向消息。次优条目的主要判断标准是:输入设备和输出设备相同。
ICMPv4重定向消息的代码有4种:
ICMP_REDIR_NET
:重定向网络;
ICMP_REDIR_HOST
:重定向主机;
ICMP_REDIR_NETTOS
:针对TOS重定向网络;
ICMP_REDIR_HOSTTOS
:针对TOS重定向主机;
1、生成ICMPv4重定向消息
存在次优路由时,将发送 ICMPv4 重定向消息。判断次优路由的最重要标准是:输入设备和输出设备相同。但同时还需要满足其他一些条件。ICMPv4重定向消息的生成过程两个阶段:__mkroute_input()
设置标志RTCF_DOREDIRECT
------>ip_forward()
发送 ICMPv4 定向消息。
/* called in rcu_read_lock() section */
static int __mkroute_input(struct sk_buff *skb,
const struct fib_result *res,
struct in_device *in_dev,
__be32 daddr, __be32 saddr, u32 tos)
{
struct fib_nh_exception *fnhe;
struct rtable *rth;
int err;
struct in_device *out_dev;
bool do_cache;
u32 itag = 0;
/* get a working reference to the output device */
out_dev = __in_dev_get_rcu(FIB_RES_DEV(*res));
if (!out_dev) {
net_crit_ratelimited("Bug in ip_route_input_slow(). Please report.\n");
return -EINVAL;
}
err = fib_validate_source(skb, saddr, daddr, tos, FIB_RES_OIF(*res),
in_dev->dev, in_dev, &itag);
if (err < 0) {
ip_handle_martian_source(in_dev->dev, in_dev, skb, daddr,
saddr);
goto cleanup;
}
do_cache = res->fi && !itag;
if (out_dev == in_dev && err && IN_DEV_TX_REDIRECTS(out_dev) &&
skb->protocol == htons(ETH_P_IP) &&
(IN_DEV_SHARED_MEDIA(out_dev) ||
inet_addr_onlink(out_dev, saddr, FIB_RES_GW(*res))))
IPCB(skb)->flags |= IPSKB_DOREDIRECT;
if (skb->protocol != htons(ETH_P_IP)) {
/* Not IP (i.e. ARP). Do not create route, if it is
* invalid for proxy arp. DNAT routes are always valid.
*
* Proxy arp feature have been extended to allow, ARP
* replies back to the same interface, to support
* Private VLAN switch technologies. See arp.c.
*/
if (out_dev == in_dev &&
IN_DEV_PROXY_ARP_PVLAN(in_dev) == 0) {
err = -EINVAL;
goto cleanup;
}
}
fnhe = find_exception(&FIB_RES_NH(*res), daddr);
if (do_cache) {
if (fnhe) {
rth = rcu_dereference(fnhe->fnhe_rth_input);
if (rth && rth->dst.expires &&
time_after(jiffies, rth->dst.expires)) {
ip_del_fnhe(&FIB_RES_NH(*res), daddr);
fnhe = NULL;
} else {
goto rt_cache;
}
}
rth = rcu_dereference(FIB_RES_NH(*res).nh_rth_input);
rt_cache:
if (rt_cache_valid(rth)) {
skb_dst_set_noref(skb, &rth->dst);
goto out;
}
}
rth = rt_dst_alloc(out_dev->dev, 0, res->type,
IN_DEV_CONF_GET(in_dev, NOPOLICY),
IN_DEV_CONF_GET(out_dev, NOXFRM), do_cache);
if (!rth) {
err = -ENOBUFS;
goto cleanup;
}
rth->rt_is_input = 1;
if (res->table)
rth->rt_table_id = res->table->tb_id;
RT_CACHE_STAT_INC(in_slow_tot);
rth->dst.input = ip_forward;
rt_set_nexthop(rth, daddr, res, fnhe, res->fi, res->type, itag);
set_lwt_redirect(rth);
skb_dst_set(skb, &rth->dst);
out:
err = 0;
cleanup:
return err;
}
2、接收ICMPv4重定向消息
仅当 ICMPv4 重定向消息通过一些完整性检查后,才会对其进行处理,处理 ICMPv4 重定向消息的工作是由__ip_do_redirect()
完成。
static void __ip_do_redirect(struct rtable *rt, struct sk_buff *skb, struct flowi4 *fl4,
bool kill_route)
{
__be32 new_gw = icmp_hdr(skb)->un.gateway;
__be32 old_gw = ip_hdr(skb)->saddr;
struct net_device *dev = skb->dev;
struct in_device *in_dev;
struct fib_result res;
struct neighbour *n;
struct net *net;
switch (icmp_hdr(skb)->code & 7) {
case ICMP_REDIR_NET:
case ICMP_REDIR_NETTOS:
case ICMP_REDIR_HOST:
case ICMP_REDIR_HOSTTOS:
break;
default:
return;
}
if (rt->rt_gateway != old_gw)
return;
in_dev = __in_dev_get_rcu(dev);
if (!in_dev)
return;
net = dev_net(dev);
if (new_gw == old_gw || !IN_DEV_RX_REDIRECTS(in_dev) ||
ipv4_is_multicast(new_gw) || ipv4_is_lbcast(new_gw) ||
ipv4_is_zeronet(new_gw))
goto reject_redirect;
if (!IN_DEV_SHARED_MEDIA(in_dev)) {
if (!inet_addr_onlink(in_dev, new_gw, old_gw))
goto reject_redirect;
if (IN_DEV_SEC_REDIRECTS(in_dev) && ip_fib_check_default(new_gw, dev))
goto reject_redirect;
} else {
if (inet_addr_type(net, new_gw) != RTN_UNICAST)
goto reject_redirect;
}
n = __ipv4_neigh_lookup(rt->dst.dev, new_gw);
if (!n)
n = neigh_create(&arp_tbl, &new_gw, rt->dst.dev);
if (!IS_ERR(n)) {
if (!(n->nud_state & NUD_VALID)) {
neigh_event_send(n, NULL);
} else {
if (fib_lookup(net, fl4, &res, 0) == 0) {
struct fib_nh *nh = &FIB_RES_NH(res);
update_or_create_fnhe(nh, fl4->daddr, new_gw,
0, jiffies + ip_rt_gc_timeout);
}
if (kill_route)
rt->dst.obsolete = DST_OBSOLETE_KILL;
call_netevent_notifiers(NETEVENT_NEIGH_UPDATE, n);
}
neigh_release(n);
}
return;
reject_redirect:
#ifdef CONFIG_IP_ROUTE_VERBOSE
if (IN_DEV_LOG_MARTIANS(in_dev)) {
const struct iphdr *iph = (const struct iphdr *) skb->data;
__be32 daddr = iph->daddr;
__be32 saddr = iph->saddr;
net_info_ratelimited("Redirect from %pI4 on %s about %pI4 ignored\n"
" Advised path = %pI4 -> %pI4\n",
&old_gw, dev->name, &new_gw,
&saddr, &daddr);
}
#endif
;
}