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);  //关闭ipv6elseaddrconf_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_UPif (!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;
#endifcase 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 processrouter 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); //发送RSwrite_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;}}
#endifif (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 updatestandard 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与RA的一点学习相关推荐

  1. Android驱动(1)---Ubuntu中为Android系统上编写Linux内核驱动程序实现方法

    Ubuntu中为Android系统上编写Linux内核驱动程序实现方法 本文主要介绍在Ubuntu 上为Android系统编写Linux内核驱动程序, 这里对编写驱动程序做了详细的说明,对研究Andr ...

  2. linux内核中链表代码分析---list.h头文件分析(二)【转】

    转自:http://blog.chinaunix.net/uid-30254565-id-5637598.html linux内核中链表代码分析---list.h头文件分析(二) 16年2月28日16 ...

  3. android内核中Kconfig及如何加自己的驱动

    2019独角兽企业重金招聘Python工程师标准>>> 内核的源码树目录下一般都会有两个文文:Kconfig和Makefile.分布在各目录下的Kconfig构成了一个分布式的内核配 ...

  4. Android产品研发(二十一)--Android中的UI优化

    转载请标明出处:一片枫叶的专栏 上一篇文章中我们讲解了Android产品研发过程中的代码Review.通过代码Review能够提高产品质量,增强团队成员之间的沟通,提高开发效率,所以良好的产品开发迭代 ...

  5. i.MX 6ULL 驱动开发 二十九:向 Linux 内核中添加自己编写驱动

    一.概述 Linux 内核编译流程如下: 1.配置 Linux 内核. 2.编译 Linux 内核. 说明:进入 Linux 内核源码,使用 make help 参看相关配置. 二.make menu ...

  6. Linux内核中max()宏的奥妙何在?(二)——大神Linus对这个宏怎么看?

    最新max()宏 上回,我们在<Linux内核中max()宏的奥妙何在?(一)>一文中说到,在3.18.34版Linux内核源码中的max()宏,采用了GCC的扩展特性,可以避免一些错误. ...

  7. linux内核的外部接口函数,linux内核中GPIO的使用(二)--标准接口函数

    在linux内核中,有一些基本模块可以使用标准的接口函数来操作,比如GPIO.interrupt.clock,所谓的标准接口函数是指一些与硬件平台无关的.linux下做驱动通用的函数, 常用的有: g ...

  8. android 电容屏(二):驱动调试之基本概念篇

    关键词:android  电容屏 tp 工作队列 中断 多点触摸协议 平台信息: 内核:linux2.6/linux3.0 系统:android/android4.0  平台:S5PV310(sams ...

  9. (连载)Android系统源码分析--Android系统启动流程之Linux内核

    > **这是一个连载的博文系列,我将持续为大家提供尽可能透彻的Android源码分析 [github连载地址](https://github.com/foxleezh/AOSP/issues/3 ...

最新文章

  1. 3月18 周作业题解
  2. iOS朋友圈,视频播放器、钓鱼小游戏、玻璃动画源码
  3. spring mvc 文件上传 form表单
  4. Gym 100090D Insomnia
  5. Visual Studio 2010旗舰版在安装Windows Phone 7 SDK后项目模版里没有Windows Phone 项目解决办法...
  6. windows下集成maven+eclipse开发环境二:集成maven到eclipse,并使用nexus作为maven仓库...
  7. 海升集团数据上云 走出智能农业的新路子
  8. 希捷银河声音大_【推仔说新闻】那款硬盘它终于来了 希捷推出首款双磁臂硬盘...
  9. C++:通过多态实现接口并生成dll和lib文件的小例子
  10. WordPress美化_节日灯笼插件
  11. 2020年进入倒计时:一波前端资源送给你~这一年,谢谢自己!
  12. NLP简报(Issue#8)
  13. 丈夫创业前后累计11次
  14. python入门先学什么-学习python需要什么基础
  15. wps怎么修改云端服务器的地址,新版wps怎么没有云服务器
  16. ORACLE 排序函数row_number / rank / dense_rank
  17. 《国产操作系统之银河麒麟》安装VNCserver插件
  18. Java开发微信公众号-接口测试帐号接口配置及Java源代码
  19. 我们能为别人留下什么?——纪念一位真正的兄长
  20. 【论文阅读/翻译笔记】Deep Snake for Real-Time Instance Segmentation

热门文章

  1. 看看Pwn2Own黑客大赛有哪些新技术
  2. web项目图片/文件保存方式
  3. 用python做一个简单GUI小软件
  4. 代理服务器介绍及种类划分
  5. linux系统换硬件要重装,换cpu要重装系统吗?电脑更换cpu需要重装系统吗
  6. IDL CMIP6 NC格式数据处理
  7. C++中this的理解
  8. 为什么推广效果无法提升?
  9. 游戏原画之组合画法-张聪-专题视频课程
  10. AIX存储LV PV VG