设为首页 收藏本站
查看: 366|回复: 0

[经验分享] ip_forward参数对Linux内核转发影响分析

[复制链接]
累计签到:77 天
连续签到:1 天
发表于 2014-8-14 10:01:14 | 显示全部楼层 |阅读模式
                       
        在进行Linux内核转发时,需要在proc文件系统的proc/sys目录设置转发的参数,可以使用下面的方法查看该参数的值 cat /proc/sys/net/ipv4/ip_forward,该参数的默认值为0,可以使用下面的方法进行修改该值,使能Linux内核的IP层的数据抓发,但是下面的方法在系统重启后不再生效。
        echo 1 > /proc/sys/net/ipv4/ip_forward
                 在Linux系统中也提供了一个系统的配置工具sysctl,使用它可以读取和配置Linux内核的一些参数。但是该方法和proc文件系统相关,使用该工具Linux内核需要支持proc文件系统。下面是使用sysctl配置内核的转发参数。
        # sysctl net.ipv4.ip_forward
        net.ipv4.ip_forward = 0
        / # sysctl -w net.ipv4.ip_forward=1
        net.ipv4.ip_forward = 1
        / # sysctl net.ipv4.ip_forward
        net.ipv4.ip_forward = 1
        / #
        注意,参数 net.ipv4.ip_forward 实际是对应的 proc 目录/proc/sys/net/ipv4/ip_forward,选项 -w 表示配置该内核配置参数,没有选项表示读内核配置参数,不加任何选项信息,就表示读取操作。
        通过上面的方法我们可以设置和读取IP转发的参数。但是本文重点不是讲该参数如何配置,而是在配置完成后,在内核的转发过程中如何生效的,以及如何配置到内核中。既然,该参数是配置使能IP层的转发,那应该在Linux内核的转发部分对该参数进行了判断,该参数的判断实际上是在查找路由时进行判断的,下面这张图显示了其中的调用关系,
20788636_1407415204ihW6.jpg

        在查路由的过程中,如果是转发的数据包调用下面的宏判断转发的参数是否开启。在函数ip_route_input_slow。
        if (!IN_DEV_FORWARD(in_dev))
                    gotoe_hostunreach;
                 看一下该宏是如何进行定义的,下面的宏定义在include/linux/inetdevice.h文件中。
        #defineIN_DEV_FORWARD(in_dev)                 IN_DEV_CONF_GET((in_dev), FORWARDING)
        在把IN_DEV_CONF_GET宏进一步展开了看:
        #define IN_DEV_CONF_GET(in_dev, attr) \
                 ipv4_devconf_get((in_dev),NET_IPV4_CONF_ ## attr)//这里的##表示连接两个字符串。
                 下面是ipv4_devconf_get函数的定义:
        staticinline int ipv4_devconf_get(struct in_device *in_dev, int index)
        {
                 index--;//这里的index相当于NET_IPV4_CONF_FORWARDING
                 return in_dev->cnf.data[index];// init_net->ipv4.devconf_dfl.data[0]
        }
        (1)对于宏NET_IPV4_CONF_ FORWARDING,定义在include/linux/sysctl.h文件中,是一个枚举类型的。
        enum
        {
                 NET_IPV4_CONF_FORWARDING=1,
                 NET_IPV4_CONF_MC_FORWARDING=2,
                 NET_IPV4_CONF_PROXY_ARP=3,
                 NET_IPV4_CONF_ACCEPT_REDIRECTS=4,
                 NET_IPV4_CONF_SECURE_REDIRECTS=5,
                 NET_IPV4_CONF_SEND_REDIRECTS=6,
                 NET_IPV4_CONF_SHARED_MEDIA=7,
                 NET_IPV4_CONF_RP_FILTER=8,
                 NET_IPV4_CONF_ACCEPT_SOURCE_ROUTE=9,
                 NET_IPV4_CONF_BOOTP_RELAY=10,
                 NET_IPV4_CONF_LOG_MARTIANS=11,
                 NET_IPV4_CONF_TAG=12,
                 NET_IPV4_CONF_ARPFILTER=13,
                 NET_IPV4_CONF_MEDIUM_ID=14,
                 NET_IPV4_CONF_NOXFRM=15,
                 NET_IPV4_CONF_NOPOLICY=16,
                 NET_IPV4_CONF_FORCE_IGMP_VERSION=17,
                 NET_IPV4_CONF_ARP_ANNOUNCE=18,
                 NET_IPV4_CONF_ARP_IGNORE=19,
                 NET_IPV4_CONF_PROMOTE_SECONDARIES=20,
                 NET_IPV4_CONF_ARP_ACCEPT=21,
                 NET_IPV4_CONF_ARP_NOTIFY=22,
                 NET_IPV4_CONF_SRC_VMARK=24,
                 __NET_IPV4_CONF_MAX
        };
        (2)对于returnin_dev->cnf.data[index];返回的相当于in_dev->cnf.data[0],那下面我们看一下该初始值是如何产生的。
                 首先,in_dev是怎么获取到的,在ip_route_input_slow函数中通过structin_device *in_dev = in_dev_get(dev);函数获取,在in_dev_get函数中调用__in_dev_get_rcu,通过下面的赋值语句进行赋值struct in_device *in_dev = dev->ip_ptr;
        staticinline struct in_device *__in_dev_get_rcu(const struct net_device *dev)
        {
                 struct in_device *in_dev =dev->ip_ptr;
                 if (in_dev)
                           in_dev =rcu_dereference(in_dev);
                 return in_dev;
        }

        static__inline__ struct in_device *
        in_dev_get(conststruct net_device *dev)
        {
                 struct in_device *in_dev;

                 rcu_read_lock();
                 in_dev = __in_dev_get_rcu(dev);
                 if (in_dev)
                           atomic_inc(&in_dev->refcnt);
                 rcu_read_unlock();
                 return in_dev;
        }
                 dev->ip_ptr;又是什么时候赋值呢?答案是在net_device注册初始化函数inetdev_init中,
        static struct in_device*inetdev_init(struct net_device *dev)
        {
                 structin_device *in_dev;

                 ASSERT_RTNL();

                 in_dev= kzalloc(sizeof(*in_dev), GFP_KERNEL);
                 if(!in_dev)
                           gotoout;
                 memcpy(&in_dev->cnf,dev_net(dev)->ipv4.devconf_dflt,
                                    sizeof(in_dev->cnf));//这里对in_dev->cnt进行初始化操作,---(1)
                 in_dev->cnf.sysctl= NULL;
                 in_dev->dev= dev;
                 if((in_dev->arp_parms = neigh_parms_alloc(dev, &arp_tbl)) == NULL)
                           gotoout_kfree;
                 if(IPV4_DEVCONF(in_dev->cnf, FORWARDING))
                           dev_disable_lro(dev);
                 /*Reference in_dev->dev */
                 dev_hold(dev);
                 /*Account for reference dev->ip_ptr (below) */
                 in_dev_hold(in_dev);

                 devinet_sysctl_register(in_dev);
                 ip_mc_init_dev(in_dev);
                 if(dev->flags & IFF_UP)
                           ip_mc_up(in_dev);

                 /*we can receive as soon as ip_ptr is set -- do this last */
                 rcu_assign_pointer(dev->ip_ptr,in_dev);//使用RCU保护锁机制对dev->ip_ptr进行赋值
        out:
                 returnin_dev;
        out_kfree:
                 kfree(in_dev);
                 in_dev= NULL;
                 gotoout;
        }
        (1)dev_net(dev)->ipv4.devconf_dfl 也就相当于init_net->ipv4.devconf_dfl,而devconf_dfl的初始化时在/net/ipv4/devinet.c文件中,devinet_init_net函数中,
        static struct ipv4_devconfipv4_devconf_dflt = {
                 .data = {
                           [NET_IPV4_CONF_ACCEPT_REDIRECTS- 1] = 1,
                           [NET_IPV4_CONF_SEND_REDIRECTS- 1] = 1,
                           [NET_IPV4_CONF_SECURE_REDIRECTS- 1] = 1,
                           [NET_IPV4_CONF_SHARED_MEDIA- 1] = 1,
                           [NET_IPV4_CONF_ACCEPT_SOURCE_ROUTE- 1] = 1,
                 },
        };//这里并没有对FORWARDING进行赋值操作
        static __net_init intdevinet_init_net(struct net *net)
        {
                 interr;
                 structipv4_devconf *all, *dflt;
        #ifdef CONFIG_SYSCTL
                 struct ctl_table *tbl = ctl_forward_entry;
                 structctl_table_header *forw_hdr;
        #endif

                 err= -ENOMEM;
                 all = &ipv4_devconf; //----------------------------进行初始化操作
                 dflt = &ipv4_devconf_dflt;

                 if(net != &init_net) {
                           all= kmemdup(all, sizeof(ipv4_devconf), GFP_KERNEL);
                           if(all == NULL)
                                    gotoerr_alloc_all;

                           dflt= kmemdup(dflt, sizeof(ipv4_devconf_dflt), GFP_KERNEL);
                           if(dflt == NULL)
                                    gotoerr_alloc_dflt;

        #ifdef CONFIG_SYSCTL
                           tbl= kmemdup(tbl, sizeof(ctl_forward_entry), GFP_KERNEL);
                           if(tbl == NULL)
                                    gotoerr_alloc_ctl;

                           tbl[0].data= &all->data[NET_IPV4_CONF_FORWARDING - 1];
                           tbl[0].extra1= all;
                           tbl[0].extra2= net;
        #endif
                 }

        #ifdef CONFIG_SYSCTL
                 err= __devinet_sysctl_register(net, "all",
                                    NET_PROTO_CONF_ALL,all);
                 if(err < 0)
                           gotoerr_reg_all;

                 err= __devinet_sysctl_register(net, "default",
                                    NET_PROTO_CONF_DEFAULT,dflt);
                 if(err < 0)
                           gotoerr_reg_dflt;

                 err= -ENOMEM;
                 forw_hdr = register_net_sysctl_table(net, net_ipv4_path,tbl);
                 if(forw_hdr == NULL)
                           gotoerr_reg_ctl;
                 net->ipv4.forw_hdr= forw_hdr;
        #endif

                 net->ipv4.devconf_all = all;//这里对net->ipv4_devconfi_all进行了初始化
                 net->ipv4.devconf_dflt = dflt;// //这里对net->devconf_dflt进行了初始化
                 return0;
        ………………………….
        }
        上面的函数对net相关功能的初始化,在devinet.c文件中还有一个和ipv4_devconf_dflt类似的变量ipv4_devconf,但是IN_DEV_FORWARD(in_dev)宏读取的是结构体ipv4_devconf_dflt中变量的值,所以,如果要在Linux内核中修改转发的参数时,需要在ipv4_devconf_dflt中添加才能生效。
        static struct ipv4_devconf ipv4_devconf = {
                 .data = {
                           [NET_IPV4_CONF_ACCEPT_REDIRECTS- 1] = 1,
                           [NET_IPV4_CONF_SEND_REDIRECTS- 1] = 1,
                           [NET_IPV4_CONF_SECURE_REDIRECTS- 1] = 1,
                           [NET_IPV4_CONF_SHARED_MEDIA- 1] = 1,
                           [NET_IPV4_CONF_FORCE_IGMP_VERSION-1]=2,
                 },
        };
        (3)下面看一下使用echo 1 > /proc/sys/net/ipv4/ip_forward配置语句如何是Linux内核IP转发生效的。
              在上面的devinet_init_net()函数中,有下面的两段代码
                 struct ctl_table *tbl = ctl_forward_entry;
                 forw_hdr = register_net_sysctl_table(net, net_ipv4_path,tbl);
        其中ctl_forward_entry定义为下面的结构,
        static structctl_table ctl_forward_entry[] = {
                 {
                           .ctl_name         = NET_IPV4_FORWARD,//一个ID,
                           .procname        = "ip_forward",//字符串,包含在proc/sys下目录项,实际为proc/sys目录下的文件名
                           .data                  = &ipv4_devconf.data[
                                                       NET_IPV4_CONF_FORWARDING- 1],//回调函数设置的值
                           .maxlen             = sizeof(int),//设置值的最大长度
                           .mode                = 0644,//文件的权限,也就是ip_forward文件的权限
                           .proc_handler  = devinet_sysctl_forward,// 对/proc/sys下面的文件修改的时候调用该回调函数。
                           .strategy = devinet_conf_sysctl,// 用sysctl读写系统参数时候调用该回调函数
                           .extra1              = &ipv4_devconf,
                           .extra2              = &init_net,
                 },
                 { },
        };
                 forw_hdr =register_net_sysctl_table(net, net_ipv4_path, tbl);用于动态注册系统控制功能,其中net_ipv4_path定义为下面的形式。也就是proc/sys/下的目录名,tbl就是上面的ctl_forward_entry[]结构体。
        static __net_initdata struct ctl_pathnet_ipv4_path[] = {
                 {.procname = "net", .ctl_name = CTL_NET, },
                 {.procname = "ipv4", .ctl_name = NET_IPV4, },
                 {},
        };
                 使用echo 1 >/proc/sys/net/ipv4/ip_forward调用devinet_sysctl_forward函数进行处理,下面是该函数的定义实现。其中参数write为1表示写配置,为0表示读取配置值,buffer是要写的值,lenp为buffer的大小,ppos为位置。这里的__user是告诉不应该解除该指针的引用,因为在当前地址空间中它是没有意义的,所以对于这种变量,在kernel中使用要用到copy_to_user和copy_from_user
        staticint devinet_sysctl_forward(ctl_table *ctl, int write,
                                               void __user *buffer,
                                               size_t *lenp, loff_t *ppos)
        {
                 int *valp = ctl->data;//获取&ipv4_devconf.data地址
                 int val = *valp;
                 loff_t pos = *ppos;
                 int ret = proc_dointvec(ctl, write,buffer, lenp, ppos);//该函数处理传进来的int型,proc_dostring处理传过来的字符串。
        /*ctl->data change  echo "0">/proc/sys/net/ipv4/ip_forward  write= 1 *valp = 0 val = 1 */
                 if (write && *valp != val) {
                           struct net *net =ctl->extra2;

                           if (valp !=&IPV4_DEVCONF_DFLT(net, FORWARDING)) {
                                    if (!rtnl_trylock()){
                                             /*Restore the original values before restarting */
                                             *valp =val;
                                             *ppos =pos;
                                             returnrestart_syscall();
                                    }
                                    if (valp ==&IPV4_DEVCONF_ALL(net, FORWARDING)) {
                                             inet_forward_change(net);//调用该函数进行配置in_dev->cnf.data
                                    }else if (*valp) {
                                             structipv4_devconf *cnf = ctl->extra1;
                                             structin_device *idev =
                                                       container_of(cnf,struct in_device, cnf);
                                             dev_disable_lro(idev->dev);
                                    }
                                    rtnl_unlock();
                                    rt_cache_flush(net,0);
                           }
                 }

                 return ret;
        }
        下面是这个函数就是修改forward参数,
        static void inet_forward_change(struct net *net)
        {
                 struct net_device*dev;
                 int on = IPV4_DEVCONF_ALL(net, FORWARDING);//获取配置的值

                 IPV4_DEVCONF_ALL(net,ACCEPT_REDIRECTS) = !on;
                 IPV4_DEVCONF_DFLT(net,FORWARDING) = on;//设置ipv4_devconf_dflt结构体,

                 read_lock(&dev_base_lock);
                 for_each_netdev(net,dev) {
                           struct in_device*in_dev;
                           if (on)
                                    dev_disable_lro(dev);
                           rcu_read_lock();
                           in_dev =__in_dev_get_rcu(dev);
                           if (in_dev)
                                    IN_DEV_CONF_SET(in_dev,FORWARDING, on);//调用该宏设置in_dev->cnf.data
                           rcu_read_unlock();
                 }
                 read_unlock(&dev_base_lock);
        }

        static inline void ipv4_devconf_set(struct in_device *in_dev, intindex,
                                                 int val)
        {
                 index--;
                 set_bit(index,in_dev->cnf.state);
                 in_dev->cnf.data[index] = val;//设置in_dev的data,这里的Index为NET_IPV4_CONF_FORWARDING
        }
        其调用关系如下图:

        20788636_1407415228jLPf.jpg
           


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-23775-1-1.html 上篇帖子: 命令行创建硬Raid 下篇帖子: 关于分布式拒绝攻击(DDOS)软件tfn2k的编译及使用
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表