IPv6设备配置选项

IPv6子系统中的三个全局配置:ipv6_devconf、ipv6_devconf_dflt和ipv6_defaults:

static struct ipv6_devconf ipv6_devconf __read_mostly = {
    .forwarding     = 0,
    .hop_limit      = IPV6_DEFAULT_HOPLIMIT,
	...
}
static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
    .forwarding     = 0,
    .hop_limit      = IPV6_DEFAULT_HOPLIMIT,
	...
}
struct ipv6_params ipv6_defaults = {
    .disable_ipv6 = 0,
    .autoconf = 1,
};  

在命名空间初始化函数addrconf_init_net中,第一个配置ipv6_devconf,对应于all;第二个配置对应于默认dflt。

static int __net_init addrconf_init_net(struct net *net)
{
    int err = -ENOMEM;
    struct ipv6_devconf *all, *dflt;

    all = kmemdup(&ipv6_devconf, sizeof(ipv6_devconf), GFP_KERNEL);
    if (!all) goto err_alloc_all;

    dflt = kmemdup(&ipv6_devconf_dflt, sizeof(ipv6_devconf_dflt), GFP_KERNEL);
    if (!dflt) goto err_alloc_dflt;

二者在PROC文件系统中,对应于以下目录,其中的子目录对应于每个配置项。

$ cat /proc/sys/net/ipv6/conf/all/   
$ cat /proc/sys/net/ipv6/conf/default/

如果当前正在初始化的命名空间不是默认的init_net,根据sysctl_devconf_inherit_init_net的不同值,执行不同的操作。当其值为1时,all和dflt使用init_net中的值。

当其值为3时,使用当前进程所属的命名空间中的all和dflt的值。

    if (IS_ENABLED(CONFIG_SYSCTL) && !net_eq(net, &init_net)) {
        switch (sysctl_devconf_inherit_init_net) {
        case 1:  /* copy from init_net */
            memcpy(all, init_net.ipv6.devconf_all, sizeof(ipv6_devconf));
            memcpy(dflt, init_net.ipv6.devconf_dflt, sizeof(ipv6_devconf_dflt));
            break;
        case 3: /* copy from the current netns */
            memcpy(all, current->nsproxy->net_ns->ipv6.devconf_all, sizeof(ipv6_devconf));
            memcpy(dflt, current->nsproxy->net_ns->ipv6.devconf_dflt, sizeof(ipv6_devconf_dflt));
            break;
        case 0:
        case 2:
            /* use compiled values */
            break;
        }
    }

以上提到的ipv6_defaults变量中的值具有高优先级,其当前仅有两个子成员autoconf和disable_ipv6,覆盖dflt结构中的相应值。最后,将all和dflt注册到PROC文件系统中,这样就可在目录/proc/sys/net/ipv6/conf/中看到对应的all和default子目录了。

    /* these will be inherited by all namespaces */
    dflt->autoconf = ipv6_defaults.autoconf;
    dflt->disable_ipv6 = ipv6_defaults.disable_ipv6;

    dflt->stable_secret.initialized = false;
    all->stable_secret.initialized = false;

    net->ipv6.devconf_all = all;
    net->ipv6.devconf_dflt = dflt;

#ifdef CONFIG_SYSCTL
    err = __addrconf_sysctl_register(net, "all", NULL, all);
    if (err < 0)
        goto err_reg_all;

    err = __addrconf_sysctl_register(net, "default", NULL, dflt);
    if (err < 0)
        goto err_reg_dflt;
#endif

变量devconf_inherit_init_net的值默认为零,可以通过相应的PROC文件修改其值。对于IPv6而言,值0和2都会复位到devconf的初始值。值1会继承init_net中的当前值,值3将继承当前进程所属网络命名空间中的值。

对于IPv4,当值为0时,也会继承init_net中的当前值,这点与IPv6不同。

/* 0 - Keep current behavior:
 *     IPv4: inherit all current settings from init_net
 *     IPv6: reset all settings to default
 * 1 - Both inherit all current settings from init_net
 * 2 - Both reset all settings to default
 * 3 - Both inherit all settings from current netns
 */
int sysctl_devconf_inherit_init_net __read_mostly;
EXPORT_SYMBOL(sysctl_devconf_inherit_init_net);

$ cat /proc/sys/net/core/devconf_inherit_init_net
0

SYSCTL注册配置

首先,看一下全局结构体变量addrconf_sysctl,其结构为SYSCTL系统ctl_table结构,内容如下,注意其每个配置项的data字段,地址指向的为ipv6_devconf变量中的子成员地址。

由上节可知,ipv6_devconf变量对应于all配置。

static const struct ctl_table addrconf_sysctl[] = {
    {
        .procname   = "forwarding",
        .data       = &ipv6_devconf.forwarding,
        .maxlen     = sizeof(int),
        .mode       = 0644,
        .proc_handler   = addrconf_sysctl_forward,
    },
    {
        .procname   = "hop_limit",
        .data       = &ipv6_devconf.hop_limit,
        .maxlen     = sizeof(int),
        .mode       = 0644,
        .proc_handler   = proc_dointvec_minmax,
        .extra1     = (void *)SYSCTL_ONE,
        .extra2     = (void *)&two_five_five,
    },

接下来,看一下上节函数addrconf_init_net将all和dflt注册到sysctl系统的函数,首先,拷贝一份addrconf_sysctl全局变量,。

static int __addrconf_sysctl_register(struct net *net, char *dev_name,
        struct inet6_dev *idev, struct ipv6_devconf *p)
{
    int i, ifindex;
    struct ctl_table *table;
    char path[sizeof("net/ipv6/conf/") + IFNAMSIZ];

    table = kmemdup(addrconf_sysctl, sizeof(addrconf_sysctl), GFP_KERNEL);
    if (!table) goto out;

由于addrconf_sysctl结构的表项中的data字段默认都指向ipv6_devconf结构中的成员值,这里需要对其进行更新,将其指向all或者dflt(p参数)中成员的地址。如下,只需要将data偏移两个全局变量之间的差值即可。

    for (i = 0; table[i].data; i++) {
        table[i].data += (char *)p - (char *)&ipv6_devconf;
        /* If one of these is already set, then it is not safe to
         * overwrite either of them: this makes proc_dointvec_minmax usable.
         */
        if (!table[i].extra1 && !table[i].extra2) {
            table[i].extra1 = idev; /* embedded; no ref */
            table[i].extra2 = net;
        }
    }

    snprintf(path, sizeof(path), "net/ipv6/conf/%s", dev_name);

    p->sysctl_header = register_net_sysctl(net, path, table);
    if (!p->sysctl_header)
        goto free;

    if (!strcmp(dev_name, "all"))
        ifindex = NETCONFA_IFINDEX_ALL;
    else if (!strcmp(dev_name, "default"))
        ifindex = NETCONFA_IFINDEX_DEFAULT;
    else
        ifindex = idev->dev->ifindex;
    inet6_netconf_notify_devconf(net, RTM_NEWNETCONF, NETCONFA_ALL, ifindex, p);

接口配置

除了以上的all和dflt配置结构,每个网络接口也有一套自身的配置项,如在接口注册时,在函数ipv6_add_dev中初见inet6_dev时,调用addrconf_sysctl_register函数注册sysctl配置项。

static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
{
    struct inet6_dev *ndev;

    if (dev->mtu < IPV6_MIN_MTU) return ERR_PTR(-EINVAL);

    ndev = kzalloc(sizeof(struct inet6_dev), GFP_KERNEL);
    if (!ndev) return ERR_PTR(err);

    ipv6_mc_init_dev(ndev);
    ndev->tstamp = jiffies;
    err = addrconf_sysctl_register(ndev);

此时,在PROC系统下,创建的目录名称为网络接口的名称idev->dev->name,存储配置的变量为接口自身的idev->cnf。

static int addrconf_sysctl_register(struct inet6_dev *idev) 
{
    err = __addrconf_sysctl_register(dev_net(idev->dev), idev->dev->name,
                     idev, &idev->cnf); 

例如,对于接口ens33,创建以下的配置目录。

$ cat /proc/sys/net/ipv6/conf/ens33/

配置项的使用

以配置项forwarding为例,当接口添加或者删除地址时,根据设备自身的配置项forwarding,来决定是否进行anycast地址的相关处理。

static void __ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp)
{
    struct net *net = dev_net(ifp->idev->dev);

    switch (event) {
    case RTM_NEWADDR:
        ...
        if (ifp->idev->cnf.forwarding)
            addrconf_join_anycast(ifp);

        break;
    case RTM_DELADDR:
        if (ifp->idev->cnf.forwarding)
            addrconf_leave_anycast(ifp);

在添加本地链路地址时,只有当接口所在网络命名空间中all配置下的forwarding置位,本地链路地址才有可能设置上IFA_F_OPTIMISTIC标志。

void addrconf_add_linklocal(struct inet6_dev *idev, const struct in6_addr *addr, u32 flags)
{
    struct ifa6_config cfg = {
        .pfx = addr,
        .plen = 64,
        .ifa_flags = flags | IFA_F_PERMANENT,
        .valid_lft = INFINITY_LIFE_TIME,
        .preferred_lft = INFINITY_LIFE_TIME,
        .scope = IFA_LINK
    };
    struct inet6_ifaddr *ifp;

#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
    if ((dev_net(idev->dev)->ipv6.devconf_all->optimistic_dad ||
         idev->cnf.optimistic_dad) &&
        !dev_net(idev->dev)->ipv6.devconf_all->forwarding)
        cfg.ifa_flags |= IFA_F_OPTIMISTIC;
#endif
    ifp = ipv6_add_addr(idev, &cfg, true, NULL);

内核版本 5.10

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页