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