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