conf/all/forwarding配置全局的接口间IPv6报文转发,默认值为0,此值的修改将同时修改每个接口的forwarding配置项。
接口目录下forwarding配置项对于接口的Host/Router行为,默认值为0,遵循以下主机行为:
1)在邻居发现协议的Neighbour Advertisements报文中不设置IsRouter标志;
2)如果accept_ra为真(默认值),可发送Router Solicitations报文;
3)如果accept_ra为真(默认值),可接收Router Advertisements报文(包括执行地址自动配置);
4)如果accept_redirects为真(默认值),接收重定向报文。
如果接口配置的forwarding为真,接口执行路由器操作,与以上主机行为相反:
1)在邻居发现协议的Neighbour Advertisements报文中设置IsRouter标志;
2)如果accept_ra不等于2,不发送Router Solicitations报文;
3)如果accept_ra不等于2,忽略Router Advertisements报文;
4)忽略邻居发现需要的重定向报文。
以下为代码中相关配置参数的默认值:
static struct ipv6_devconf ipv6_devconf __read_mostly = {
.forwarding = 0,
.accept_ra = 1,
.accept_redirects = 1,
.autoconf = 1,
static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
.forwarding = 0,
.accept_ra = 1,
.accept_redirects = 1,
.autoconf = 1,
forwarding配置
如下forwarding配置项的处理函数,对于写操作,由函数addrconf_fixup_forwarding处理。
static int addrconf_sysctl_forward(struct ctl_table *ctl, int write,
void *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.forwarding, 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_fixup_forwarding(ctl, valp, val);
if (ret)
*ppos = pos;
return ret;
}
如果修改的是默认default目录下的forwarding配置,在判断值改变之后,发送RTM_NEWNETCONF消息,结束处理。对于修改all目录下forwarding配置的情况,将连带修改默认default目录下的forwarding值,如果值发送变化,发送RTM_NEWNETCONF消息。具体由addrconf_forward_change处理。
另外,对于修改具体设备目录下forwarding的情况,由函数dev_forward_change处理。
static int addrconf_fixup_forwarding(struct ctl_table *table, int *p, int newf)
{
net = (struct net *)table->extra2;
old = *p;
*p = newf;
if (p == &net->ipv6.devconf_dflt->forwarding) {
if ((!newf) ^ (!old))
inet6_netconf_notify_devconf(net, RTM_NEWNETCONF,
NETCONFA_FORWARDING, NETCONFA_IFINDEX_DEFAULT,
net->ipv6.devconf_dflt);
rtnl_unlock();
return 0;
}
if (p == &net->ipv6.devconf_all->forwarding) {
int old_dflt = net->ipv6.devconf_dflt->forwarding;
net->ipv6.devconf_dflt->forwarding = newf;
if ((!newf) ^ (!old_dflt))
inet6_netconf_notify_devconf(net, RTM_NEWNETCONF,
NETCONFA_FORWARDING,
NETCONFA_IFINDEX_DEFAULT,
net->ipv6.devconf_dflt);
addrconf_forward_change(net, newf);
if ((!newf) ^ (!old))
inet6_netconf_notify_devconf(net, RTM_NEWNETCONF,
NETCONFA_FORWARDING,
NETCONFA_IFINDEX_ALL,
net->ipv6.devconf_all);
} else if ((!newf) ^ (!old))
dev_forward_change((struct inet6_dev *)table->extra1);
rtnl_unlock();
if (newf)
rt6_purge_dflt_routers(net);
return 1;
}
遍历网络命名空间中的所有设备,设置新的forwarding值,如果其发生改变,由函数dev_forward_change进行处理。
static void addrconf_forward_change(struct net *net, __s32 newf)
{
struct net_device *dev;
struct inet6_dev *idev;
for_each_netdev(net, dev) {
idev = __in6_dev_get(dev);
if (idev) {
int changed = (!idev->cnf.forwarding) ^ (!newf);
idev->cnf.forwarding = newf;
if (changed)
dev_forward_change(idev);
}
}
}
对于支持多播的设备,forwarding配置项的改变,意味着加入或者离开IPv6链路/接口/站点本地所有路由器组播地址。另外,对于非tentative的有效地址,需要加入或者离开anycast地址。
static void dev_forward_change(struct inet6_dev *idev)
{
struct net_device *dev;
struct inet6_ifaddr *ifa;
if (!idev) return;
dev = idev->dev;
if (idev->cnf.forwarding)
dev_disable_lro(dev);
if (dev->flags & IFF_MULTICAST) {
if (idev->cnf.forwarding) {
ipv6_dev_mc_inc(dev, &in6addr_linklocal_allrouters);
ipv6_dev_mc_inc(dev, &in6addr_interfacelocal_allrouters);
ipv6_dev_mc_inc(dev, &in6addr_sitelocal_allrouters);
} else {
ipv6_dev_mc_dec(dev, &in6addr_linklocal_allrouters);
ipv6_dev_mc_dec(dev, &in6addr_interfacelocal_allrouters);
ipv6_dev_mc_dec(dev, &in6addr_sitelocal_allrouters);
}
}
list_for_each_entry(ifa, &idev->addr_list, if_list) {
if (ifa->flags&IFA_F_TENTATIVE)
continue;
if (idev->cnf.forwarding)
addrconf_join_anycast(ifa);
else
addrconf_leave_anycast(ifa);
}
inet6_netconf_notify_devconf(dev_net(dev), RTM_NEWNETCONF,
NETCONFA_FORWARDING, dev->ifindex, &idev->cnf);
地址添加
当forwarding为真时,工作在路由器模式,对于SLAAC,不能设置地址的IFA_F_OPTIMISTIC标志,即不能使用optimistic地址,只有主机模式才能使用optimistic地址。
int addrconf_prefix_rcv_add_addr(struct net *net, struct net_device *dev,
const struct prefix_info *pinfo, struct inet6_dev *in6_dev,
const struct in6_addr *addr, int addr_type,
u32 addr_flags, bool sllao, bool tokenized,
__u32 valid_lft, u32 prefered_lft)
{
struct inet6_ifaddr *ifp = ipv6_get_ifaddr(net, addr, dev, 1);
int create = 0;
if (!ifp && valid_lft) {
int max_addresses = in6_dev->cnf.max_addresses;
struct ifa6_config cfg = {
.pfx = addr,
.plen = pinfo->prefix_len,
.ifa_flags = addr_flags,
.valid_lft = valid_lft,
.preferred_lft = prefered_lft,
.scope = addr_type & IPV6_ADDR_SCOPE_MASK,
};
#ifdef CONFIG_IPV6_OPTIMISTIC_DAD
if ((net->ipv6.devconf_all->optimistic_dad ||
in6_dev->cnf.optimistic_dad) &&
!net->ipv6.devconf_all->forwarding && sllao)
cfg.ifa_flags |= IFA_F_OPTIMISTIC;
#endif
对于链路本地地址,处理同上,不能设置地址的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
对于用户配置的地址,如果设备的forwarding为真,关闭LRO(Large Receive Offload),其只在需要接口之间转发报文时使用。另外,对于路由模式,需要加入linklocal_allrouters组播地址。
static struct inet6_dev *ipv6_add_dev(struct net_device *dev)
{
struct inet6_dev *ndev;
ndev = kzalloc(sizeof(struct inet6_dev), GFP_KERNEL);
...
if (ndev->cnf.forwarding)
dev_disable_lro(dev);
/* Join interface-local all-node multicast group */
ipv6_dev_mc_inc(dev, &in6addr_interfacelocal_allnodes);
/* Join all-node multicast group */
ipv6_dev_mc_inc(dev, &in6addr_linklocal_allnodes);
/* Join all-router multicast group if forwarding is set */
if (ndev->cnf.forwarding && (dev->flags & IFF_MULTICAST))
ipv6_dev_mc_inc(dev, &in6addr_linklocal_allrouters);
IsRouter标志
在DAD结束之后,如果需要发送非请求的NA报文,由函数ndisc_send_na处理,参数ifp->idev->cnf.forwarding的值决定是否设置报文中的IsRouter标志。
static void addrconf_dad_completed(struct inet6_ifaddr *ifp, bool bump_id, bool send_na)
{
struct net_device *dev = ifp->idev->dev;
struct in6_addr lladdr;
...
/* send unsolicited NA if enabled */
if (send_na &&
(ifp->idev->cnf.ndisc_notify ||
dev_net(dev)->ipv6.devconf_all->ndisc_notify)) {
ndisc_send_na(dev, &in6addr_linklocal_allnodes, &ifp->addr,
/*router=*/ !!ifp->idev->cnf.forwarding,
/*solicited=*/ false, /*override=*/ true,
/*inc_opt=*/ true);
}
anycast
对于新增的地址(RTM_NEWADDR),如果其所在设备打开了forwarding配置,加入地址对应的anycast。反之,如果删除地址(RTM_DELADDR),需要离开对应的anycast。工作在主机模式,不需要anycast。
static void __ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp)
{
struct net *net = dev_net(ifp->idev->dev);
if (event) ASSERT_RTNL();
inet6_ifa_notify(event ? : RTM_NEWADDR, ifp);
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);
addrconf_leave_solict(ifp->idev, &ifp->addr);
在接口down或者注销时,遍历接口上的所有地址,如果网络命名空间或者设备自身设置了down时保留地址的配置,以下函数可能不删除地址,而是离开地址对于的anycast组。
static int addrconf_ifdown(struct net_device *dev, bool unregister)
{
list_for_each_entry_safe(ifa, tmp, &idev->addr_list, if_list) {
struct fib6_info *rt = NULL;
bool keep;
addrconf_del_dad_work(ifa);
keep = keep_addr && (ifa->flags & IFA_F_PERMANENT) && !addr_is_local(&ifa->addr);
if (keep) {
/* set state to skip the notifier below */
state = INET6_IFADDR_STATE_DEAD;
ifa->state = INET6_IFADDR_STATE_PREDAD;
if (!(ifa->flags & IFA_F_NODAD)) ifa->flags |= IFA_F_TENTATIVE;
rt = ifa->rt;
ifa->rt = NULL;
} else {
state = ifa->state;
ifa->state = INET6_IFADDR_STATE_DEAD;
}
...
if (state != INET6_IFADDR_STATE_DEAD) {
__ipv6_ifa_notify(RTM_DELADDR, ifa);
inet6addr_notifier_call_chain(NETDEV_DOWN, ifa);
} else {
if (idev->cnf.forwarding)
addrconf_leave_anycast(ifa);
addrconf_leave_solict(ifa->idev, &ifa->addr);
}
内核版本 5.10