RS:Router Solicitation路由器请求
RA:Router Advertisement路由器公告
在Android系统中我们想要打开一个网络接口(比如eth0)的ipv6功能,用命令的话我们有如下两种办法:
1,echo 0 > /proc/sys/net/ipv6/conf/eth0/disable_ipv6
直接读写的内核proc文件
2,ndc interface ipv6 eth0 enable
用ndc命令,最终也是写内核proc文件
如上的操作proc文件就是触发网络终端发送RS报文的方式,接下来就记录一下内核中对应的代码流程:
写disable_ipv6文件对应的文件系统函数为:
addrconf_sysctl_disable

static
int addrconf_sysctl_disable(struct ctl_table *ctl, int write,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int *valp = ctl->data;
int val = *valp;
loff_t pos = *ppos;
struct ctl_table lctl;
int ret;
/*
* ctl->data points to idev->cnf.disable_ipv6, we should
* not modify it until we get the rtnl lock.
*/
lctl = *ctl;
lctl.data = &val;
ret = proc_dointvec(&lctl, write, buffer, lenp, ppos);
if (write)
ret = addrconf_disable_ipv6(ctl, valp, val);
if (ret)
*ppos = pos;
return ret;
}
接下来进入addrconf_disable_ipv6
static int addrconf_disable_ipv6(struct ctl_table *table, int *p, int newf)
{
struct net *net;
int old;
if (!rtnl_trylock())
return restart_syscall();
net = (struct net *)table->extra2;
old = *p;
*p = newf;
//如果值不变则不作处理
if (p == &net->ipv6.devconf_dflt->disable_ipv6) {
rtnl_unlock();
return 0;
}
if (p == &net->ipv6.devconf_all->disable_ipv6) {
net->ipv6.devconf_dflt->disable_ipv6 = newf;
addrconf_disable_change(net, newf);
} else if ((!newf) ^ (!old)) //如果值发生变化则作处理
dev_disable_change((struct inet6_dev *)table->extra1);
rtnl_unlock();
return 0;
}
接下来进入dev_disable_change
static void dev_disable_change(struct inet6_dev *idev)
{
struct netdev_notifier_info info;
if (!idev || !idev->dev)
return;
netdev_notifier_info_init(&info, idev->dev);
if (idev->cnf.disable_ipv6)
addrconf_notify(NULL, NETDEV_DOWN, &info); //关闭ipv6
else
addrconf_notify(NULL, NETDEV_UP, &info); //开启ipv6
}
这里只看开启ipv6,所以接下来看addrconf_notify对NETDEV_UP的处理
case NETDEV_UP:
case NETDEV_CHANGE:
if (dev->flags & IFF_SLAVE)
break;
if (idev && idev->cnf.disable_ipv6)
break;
if (event == NETDEV_UP) { //NETDEV_UP
if (!addrconf_qdisc_ok(dev)) {
/* device is not ready yet. */
pr_info("ADDRCONF(NETDEV_UP): %s: link is not ready\n",
dev->name);
break;
}
if (!idev && dev->mtu >= IPV6_MIN_MTU)
idev = ipv6_add_dev(dev); //将dev加入ipv6协议栈
if (!IS_ERR_OR_NULL(idev)) {
idev->if_flags |= IF_READY;
run_pending = 1;
}
} else {
if (!addrconf_qdisc_ok(dev)) {
/* device is still not ready. */
break;
}
if (idev) {
if (idev->if_flags & IF_READY)
/* device is already configured. */
break;
idev->if_flags |= IF_READY;
}
pr_info("ADDRCONF(NETDEV_CHANGE): %s: link becomes ready\n",
dev->name);
run_pending = 1;
}
switch (dev->type) {
#if IS_ENABLED(CONFIG_IPV6_SIT)
case ARPHRD_SIT:
addrconf_sit_config(dev);
break;
#endif
#if IS_ENABLED(CONFIG_NET_IPGRE)
case ARPHRD_IPGRE:
addrconf_gre_config(dev);
break;
#endif
case ARPHRD_LOOPBACK:
init_loopback(dev);
break;
default:
addrconf_dev_config(dev); //生成本地链路地址
break;
}
if (!IS_ERR_OR_NULL(idev)) {
if (run_pending)
addrconf_dad_run(idev); //准备跑DAD检测本地链路地址唯一性
/*
* If the MTU changed during the interface down,
* when the interface up, the changed MTU must be
* reflected in the idev as well as routers.
*/
if (idev->cnf.mtu6 != dev->mtu &&
dev->mtu >= IPV6_MIN_MTU) {
rt6_mtu_change(dev, dev->mtu);
idev->cnf.mtu6 = dev->mtu;
}
idev->tstamp = jiffies;
inet6_ifinfo_notify(RTM_NEWLINK, idev); //netlink通信告知上层ipv6 link建立成功
/*
* If the changed mtu during down is lower than
* IPV6_MIN_MTU stop IPv6 on this interface.
*/
if (dev->mtu < IPV6_MIN_MTU)
addrconf_ifdown(dev, 1);
}
break;
对于NETDEV_UP的处理主要有三点:
1,ipv6_add_dev将网络dev加入ipv6协议栈中
2,addrconf_dev_config生成本地链路地址
3,addrconf_dad_run跑DAD检测链路地址唯一性和发送RS报文
这里只看后续发送RS报文的地方
static void addrconf_dad_work(struct work_struct *w)
{
struct inet6_ifaddr *ifp = container_of(to_delayed_work(w),
struct inet6_ifaddr,
dad_work);
struct inet6_dev *idev = ifp->idev;
struct in6_addr mcaddr;
enum {
DAD_PROCESS,
DAD_BEGIN,
DAD_ABORT,
} action = DAD_PROCESS;
rtnl_lock();
spin_lock_bh(&ifp->state_lock);
if (ifp->state == INET6_IFADDR_STATE_PREDAD) {
action = DAD_BEGIN;
ifp->state = INET6_IFADDR_STATE_DAD;
} else if (ifp->state == INET6_IFADDR_STATE_ERRDAD) {
action = DAD_ABORT;
ifp->state = INET6_IFADDR_STATE_POSTDAD;
}
spin_unlock_bh(&ifp->state_lock);
if (action == DAD_BEGIN) {
addrconf_dad_begin(ifp);
goto out;
} else if (action == DAD_ABORT) {
addrconf_dad_stop(ifp, 1);
goto out;
}
if (!ifp->dad_probes && addrconf_dad_end(ifp))
goto out;
write_lock_bh(&idev->lock);
if (idev->dead || !(idev->if_flags & IF_READY)) {
write_unlock_bh(&idev->lock);
goto out;
}
spin_lock(&ifp->lock);
if (ifp->state == INET6_IFADDR_STATE_DEAD) {
spin_unlock(&ifp->lock);
write_unlock_bh(&idev->lock);
goto out;
}
if (ifp->dad_probes == 0) {
/*
* DAD was successful
*/
ifp->flags &= ~(IFA_F_TENTATIVE|IFA_F_OPTIMISTIC|IFA_F_DADFAILED);
spin_unlock(&ifp->lock);
write_unlock_bh(&idev->lock);
addrconf_dad_completed(ifp); //DAD完成就会开始发送RS了
goto out;
}
ifp->dad_probes--;
addrconf_mod_dad_work(ifp,
NEIGH_VAR(ifp->idev->nd_parms, RETRANS_TIME));
spin_unlock(&ifp->lock);
write_unlock_bh(&idev->lock);
/* send a neighbour solicitation for our addr */
addrconf_addr_solict_mult(&ifp->addr, &mcaddr);
ndisc_send_ns(ifp->idev->dev, NULL, &ifp->addr, &mcaddr, &in6addr_any);
out:
in6_ifa_put(ifp);
rtnl_unlock();
}
接下来进入addrconf_dad_completed
static void addrconf_dad_completed(struct inet6_ifaddr *ifp)
{
struct net_device *dev = ifp->idev->dev;
struct in6_addr lladdr;
bool send_rs, send_mld;
addrconf_del_dad_work(ifp);
/*
* Configure the address for reception. Now it is valid.
*/
ipv6_ifa_notify(RTM_NEWADDR, ifp); //通过Netlink通信告知上层,生成本地链路地址成功
/* If added prefix is link local and we are prepared to process
router advertisements, start sending router solicitations.
*/
read_lock_bh(&ifp->idev->lock);
send_mld = ifp->scope == IFA_LINK && ipv6_lonely_lladdr(ifp);
send_rs = send_mld &&
ipv6_accept_ra(ifp->idev) &&
ifp->idev->cnf.rtr_solicits > 0 &&
(dev->flags&IFF_LOOPBACK) == 0;
read_unlock_bh(&ifp->idev->lock);
/* While dad is in progress mld report's source address is in6_addrany.
* Resend with proper ll now.
*/
if (send_mld)
ipv6_mc_dad_complete(ifp->idev);
if (send_rs) {
/*
* If a host as already performed a random delay
* [...] as part of DAD [...] there is no need
* to delay again before sending the first RS
*/
if (ipv6_get_lladdr(dev, &lladdr, IFA_F_TENTATIVE))
return;
ndisc_send_rs(dev, &lladdr, &in6addr_linklocal_allrouters); //发送RS
write_lock_bh(&ifp->idev->lock);
spin_lock(&ifp->lock);
ifp->idev->rs_probes = 1;
ifp->idev->if_flags |= IF_RS_SENT;
addrconf_mod_rs_timer(ifp->idev,
ifp->idev->cnf.rtr_solicit_interval);
spin_unlock(&ifp->lock);
write_unlock_bh(&ifp->idev->lock);
}
}
RS请求属于邻居发现协议(Neighbor Discovery Protocol,NDP)中的一种,NDP包括路由器请求(RS),路由器通告(RA),邻居请求(NS),邻居通告(NA),重定向(Redirect),都是使用ipv6的icmp协议来通信的
接下来进入ndisc_send_rs
void ndisc_send_rs(struct net_device *dev, const struct in6_addr *saddr,
const struct in6_addr *daddr)
{
struct sk_buff *skb;
struct rs_msg *msg;
int send_sllao = dev->addr_len;
int optlen = 0;
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
/*
* According to section 2.2 of RFC 4429, we must not
* send router solicitations with a sllao from
* optimistic addresses, but we may send the solicitation
* if we don't include the sllao. So here we check
* if our address is optimistic, and if so, we
* suppress the inclusion of the sllao.
*/
if (send_sllao) {
struct inet6_ifaddr *ifp = ipv6_get_ifaddr(dev_net(dev), saddr,
dev, 1);
if (ifp) {
if (ifp->flags & IFA_F_OPTIMISTIC) {
send_sllao = 0;
}
in6_ifa_put(ifp);
} else {
send_sllao = 0;
}
}
#endif
if (send_sllao)
optlen += ndisc_opt_addr_space(dev);
skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen);
if (!skb)
return;
msg = (struct rs_msg *)skb_put(skb, sizeof(*msg));
*msg = (struct rs_msg) {
.icmph = {
.icmp6_type = NDISC_ROUTER_SOLICITATION,
},
};
if (send_sllao)
ndisc_fill_addr_option(skb, ND_OPT_SOURCE_LL_ADDR,
dev->dev_addr);
ndisc_send_skb(skb, daddr, saddr);
}
这里通过ndisc_send_skb函数就可以将RS请求封装成icmp协议报文发送出去了
关于RA报文接收流程,RA报文对于网络终端意义很大,收到RA报文之后会设置路由
既然RA也属于NDP,那么也是icmp协议来首先收包
static int icmpv6_rcv(struct sk_buff *skb)
{
struct net_device *dev = skb->dev;
struct inet6_dev *idev = __in6_dev_get(dev);
const struct in6_addr *saddr, *daddr;
struct icmp6hdr *hdr;
u8 type;
if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) {
struct sec_path *sp = skb_sec_path(skb);
int nh;
if (!(sp && sp->xvec[sp->len - 1]->props.flags &
XFRM_STATE_ICMP))
goto drop_no_count;
if (!pskb_may_pull(skb, sizeof(*hdr) + sizeof(struct ipv6hdr)))
goto drop_no_count;
nh = skb_network_offset(skb);
skb_set_network_header(skb, sizeof(*hdr));
if (!xfrm6_policy_check_reverse(NULL, XFRM_POLICY_IN, skb))
goto drop_no_count;
skb_set_network_header(skb, nh);
}
ICMP6_INC_STATS_BH(dev_net(dev), idev, ICMP6_MIB_INMSGS);
saddr = &ipv6_hdr(skb)->saddr;
daddr = &ipv6_hdr(skb)->daddr;
if (skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo)) {
LIMIT_NETDEBUG(KERN_DEBUG
"ICMPv6 checksum failed [%pI6c > %pI6c]\n",
saddr, daddr);
goto csum_error;
}
if (!pskb_pull(skb, sizeof(*hdr)))
goto discard_it;
hdr = icmp6_hdr(skb);
type = hdr->icmp6_type;
ICMP6MSGIN_INC_STATS_BH(dev_net(dev), idev, type);
switch (type) {
case ICMPV6_ECHO_REQUEST:
icmpv6_echo_reply(skb);
break;
case ICMPV6_ECHO_REPLY:
ping_rcv(skb);
break;
case ICMPV6_PKT_TOOBIG:
/* BUGGG_FUTURE: if packet contains rthdr, we cannot update
standard destination cache. Seems, only "advanced"
destination cache will allow to solve this problem
--ANK (980726)
*/
if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
goto discard_it;
hdr = icmp6_hdr(skb);
/*
* Drop through to notify
*/
case ICMPV6_DEST_UNREACH:
case ICMPV6_TIME_EXCEED:
case ICMPV6_PARAMPROB:
icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu);
break;
case NDISC_ROUTER_SOLICITATION:
case NDISC_ROUTER_ADVERTISEMENT:
case NDISC_NEIGHBOUR_SOLICITATION:
case NDISC_NEIGHBOUR_ADVERTISEMENT:
case NDISC_REDIRECT:
ndisc_rcv(skb); //分发给NDP协议处理
break;
case ICMPV6_MGM_QUERY:
igmp6_event_query(skb);
break;
case ICMPV6_MGM_REPORT:
igmp6_event_report(skb);
break;
case ICMPV6_MGM_REDUCTION:
case ICMPV6_NI_QUERY:
case ICMPV6_NI_REPLY:
case ICMPV6_MLD2_REPORT:
case ICMPV6_DHAAD_REQUEST:
case ICMPV6_DHAAD_REPLY:
case ICMPV6_MOBILE_PREFIX_SOL:
case ICMPV6_MOBILE_PREFIX_ADV:
break;
default:
/* informational */
if (type & ICMPV6_INFOMSG_MASK)
break;
LIMIT_NETDEBUG(KERN_DEBUG "icmpv6: msg of unknown type\n");
/*
* error of unknown type.
* must pass to upper level
*/
icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu);
}
kfree_skb(skb);
return 0;
csum_error:
ICMP6_INC_STATS_BH(dev_net(dev), idev, ICMP6_MIB_CSUMERRORS);
discard_it:
ICMP6_INC_STATS_BH(dev_net(dev), idev, ICMP6_MIB_INERRORS);
drop_no_count:
kfree_skb(skb);
return 0;
}
icmp协议收包后,就会根据类型分发给NDP协议处理ndisc_rcv
int ndisc_rcv(struct sk_buff *skb)
{
struct nd_msg *msg;
if (ndisc_suppress_frag_ndisc(skb))
return 0;
if (skb_linearize(skb))
return 0;
msg = (struct nd_msg *)skb_transport_header(skb);
__skb_push(skb, skb->data - skb_transport_header(skb));
if (ipv6_hdr(skb)->hop_limit != 255) {
ND_PRINTK(2, warn, "NDISC: invalid hop-limit: %d\n",
ipv6_hdr(skb)->hop_limit);
return 0;
}
if (msg->icmph.icmp6_code != 0) {
ND_PRINTK(2, warn, "NDISC: invalid ICMPv6 code: %d\n",
msg->icmph.icmp6_code);
return 0;
}
memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb));
switch (msg->icmph.icmp6_type) {
case NDISC_NEIGHBOUR_SOLICITATION:
ndisc_recv_ns(skb);
break;
case NDISC_NEIGHBOUR_ADVERTISEMENT:
ndisc_recv_na(skb);
break;
case NDISC_ROUTER_SOLICITATION:
ndisc_recv_rs(skb);
break;
case NDISC_ROUTER_ADVERTISEMENT:
ndisc_router_discovery(skb);
break;
case NDISC_REDIRECT:
ndisc_redirect_rcv(skb);
break;
}
return 0;
}
接下来ndisc_router_discovery继续处理,ndisc_router_discovery的处理过程很长,这里主要记录3点:
1,解析RA报文中的MOFlag,并上报用户层
/*
* Remember the managed/otherconf flags from most recently
* received RA message (RFC 2462) -- yoshfuji
*/
old_if_flags = in6_dev->if_flags;
in6_dev->if_flags = (in6_dev->if_flags & ~(IF_RA_MANAGED |
IF_RA_OTHERCONF)) |
(ra_msg->icmph.icmp6_addrconf_managed ?
IF_RA_MANAGED : 0) |
(ra_msg->icmph.icmp6_addrconf_other ?
IF_RA_OTHERCONF : 0);
/*
* transfer to native layer to handle this judgment
*/
inet6_ifinfo_notify(RTM_NEWLINK, in6_dev);
2,设置ipv6默认路由
ND_PRINTK(3, info, "RA: rt: %p lifetime: %d, for dev: %s\n",
rt, lifetime, skb->dev->name);
if (rt == NULL && lifetime) {
ND_PRINTK(3, info, "RA: adding default router\n");
rt = rt6_add_dflt_router(&ipv6_hdr(skb)->saddr, skb->dev, pref);
if (rt == NULL) {
ND_PRINTK(0, err,
"RA: %s failed to add default route\n",
__func__);
return;
}
neigh = dst_neigh_lookup(&rt->dst, &ipv6_hdr(skb)->saddr);
if (neigh == NULL) {
ND_PRINTK(0, err,
"RA: %s got default router without neighbour\n",
__func__);
ip6_rt_put(rt);
return;
}
neigh->flags |= NTF_ROUTER;
} else if (rt) {
rt->rt6i_flags = (rt->rt6i_flags & ~RTF_PREF_MASK) | RTF_PREF(pref);
}
if (rt)
rt6_set_expires(rt, jiffies + (HZ * lifetime));
if (ra_msg->icmph.icmp6_hop_limit) {
/* Only set hop_limit on the interface if it is higher than
* the current hop_limit.
*/
if (in6_dev->cnf.hop_limit < ra_msg->icmph.icmp6_hop_limit) {
in6_dev->cnf.hop_limit = ra_msg->icmph.icmp6_hop_limit;
} else {
ND_PRINTK(2, warn, "RA: Got route advertisement with lower hop_limit than current\n");
}
if (rt)
dst_metric_set(&rt->dst, RTAX_HOPLIMIT,
ra_msg->icmph.icmp6_hop_limit);
}
3,如果RA报文中携带地址前缀,则根据前缀生成global ipv6地址
if (in6_dev->cnf.accept_ra_pinfo && ndopts.nd_opts_pi) {
struct nd_opt_hdr *p;
for (p = ndopts.nd_opts_pi;
p;
p = ndisc_next_option(p, ndopts.nd_opts_pi_end)) {
addrconf_prefix_rcv(skb->dev, (u8 *)p,
(p->nd_opt_len) << 3,
ndopts.nd_opts_src_lladdr != NULL);
}
}
整理的收发RA,RS的代码大致流程,细节研究中

本文详细介绍了在Android系统中开启IPv6功能时,内核中的RS(Router Solicitation)和RA(Router Advertisement)处理流程。通过分析addrconf_sysctl_disable等函数,揭示了如何触发发送RS报文,以及RA报文接收后如何设置路由和生成全局IPv6地址。
2867

被折叠的 条评论
为什么被折叠?



