SIT通用隧道

内核虚拟设备 专栏收录该内容
24 篇文章 0 订阅

SIT模块不仅支持IPv6-over-IPv4封装,还支持IPv4-over-IPv4和MPLS-over-IPv4封装报文,其中IPv4-over-IPv4与IPIP隧道功能相同。对于控制接口,注册了每个网络命名空间处理结构sit_net_ops和netlink处理接口sit_link_ops。

static int __init sit_init(void)
{  
    int err;
   
    pr_info("IPv6, IPv4 and MPLS over IPv4 tunneling driver\n");
   
    err = register_pernet_device(&sit_net_ops);
    if (err < 0)
        return err;
    err = xfrm4_tunnel_register(&sit_handler, AF_INET6);
    if (err < 0) {
        pr_info("%s: can't register ip6ip4\n", __func__);
        goto xfrm_tunnel_failed;
    }
    ...
    err = rtnl_link_register(&sit_link_ops);

对于数据报文处理,由如下函数xfrm4_tunnel_register注册接口,隧道处理结构xfrm_tunnel按照优先级排序,数值小表示高优先级。sit_handler优先级为1。

static struct xfrm_tunnel sit_handler __read_mostly = {
    .handler    =   ipip6_rcv,
    .err_handler    =   ipip6_err,
    .priority   =   1,
};
int xfrm4_tunnel_register(struct xfrm_tunnel *handler, unsigned short family)
{
    struct xfrm_tunnel __rcu **pprev;
    struct xfrm_tunnel *t;
    int priority = handler->priority;

    for (pprev = fam_handlers(family); (t = rcu_dereference_protected(*pprev,
            lockdep_is_held(&tunnel4_mutex))) != NULL; pprev = &t->next) {
        if (t->priority > priority)
            break;
        if (t->priority == priority)
            goto err;
    }

    handler->next = *pprev;
    rcu_assign_pointer(*pprev, handler);

命名空间fallback设备

如下网络命名空间初始化函数sit_init_net,如果允许为每个命名空间创建单独的fallback隧道设备,sit驱动将创建设备sit0。PROC文件/proc/sys/net/core/fb_tunnels_only_for_init_net用于控制是否创建fallback设备。

sit的fallback隧道设备是命名空间本地属性,不能移动到其它的命名空间。但是,ip命令新创建的sit隧道设备不受此限制。

static struct pernet_operations sit_net_ops = {
    .init = sit_init_net,
    .exit_batch = sit_exit_batch_net,
    .id   = &sit_net_id,
    .size = sizeof(struct sit_net),
};
static int __net_init sit_init_net(struct net *net)
{
    struct sit_net *sitn = net_generic(net, sit_net_id);
    struct ip_tunnel *t;

    sitn->tunnels[0] = sitn->tunnels_wc;
    sitn->tunnels[1] = sitn->tunnels_l;
    sitn->tunnels[2] = sitn->tunnels_r;
    sitn->tunnels[3] = sitn->tunnels_r_l;

    if (!net_has_fallback_tunnels(net)) return 0;

    sitn->fb_tunnel_dev = alloc_netdev(sizeof(struct ip_tunnel), "sit0",
                       NET_NAME_UNKNOWN, ipip6_tunnel_setup);
    if (!sitn->fb_tunnel_dev) {
        err = -ENOMEM;
        goto err_alloc_dev;
    }
    dev_net_set(sitn->fb_tunnel_dev, net);
    sitn->fb_tunnel_dev->rtnl_link_ops = &sit_link_ops;
    sitn->fb_tunnel_dev->features |= NETIF_F_NETNS_LOCAL;

netlink用户接口

以下ip命令增加sit隧道。模式关键字mode的参数为sit,对应于内核结构sit_link_ops的成员kind的值。

# ip tunnel add sit1 mode sit remote 192.168.20.1 local 192.168.20.5

static struct rtnl_link_ops sit_link_ops __read_mostly = {
    .kind       = "sit",
    .maxtype    = IFLA_IPTUN_MAX,
    .policy     = ipip6_policy,
    .priv_size  = sizeof(struct ip_tunnel),
    .setup      = ipip6_tunnel_setup,
    .validate   = ipip6_validate,
    .newlink    = ipip6_newlink,
    .changelink = ipip6_changelink,
    .get_size   = ipip6_get_size,
    .fill_info  = ipip6_fill_info,
    .dellink    = ipip6_dellink,
    .get_link_net   = ip_tunnel_get_link_net,
};

函数ipip6_newlink创建新的sit隧道,首先ipip6_tunnel_locate检测隧道的重复性;之后,有函数ipip6_tunnel_create创建隧道。

static int ipip6_newlink(struct net *src_net, struct net_device *dev,
             struct nlattr *tb[], struct nlattr *data[], struct netlink_ext_ack *extack)
{
    struct net *net = dev_net(dev);
    struct ip_tunnel *nt;
    struct ip_tunnel_encap ipencap;

    nt = netdev_priv(dev);

    if (ipip6_netlink_encap_parms(data, &ipencap)) {
        err = ip_tunnel_encap_setup(nt, &ipencap);
        if (err < 0) return err;
    }

    ipip6_netlink_parms(data, &nt->parms, &nt->fwmark);

    if (ipip6_tunnel_locate(net, &nt->parms, 0))  return -EEXIST;

    err = ipip6_tunnel_create(dev);
    if (err < 0) return err;

    if (tb[IFLA_MTU]) {
        u32 mtu = nla_get_u32(tb[IFLA_MTU]);

        if (mtu >= IPV6_MIN_MTU &&
            mtu <= IP6_MAX_MTU - dev->hard_header_len)
            dev->mtu = mtu;
    }

将新注册的隧道设备进行注册(register_netdevice),之后有函数ipip6_tunnel_link将新设备连接到命名空间的链表上。

static int ipip6_tunnel_create(struct net_device *dev)
{   
    struct ip_tunnel *t = netdev_priv(dev);
    struct net *net = dev_net(dev);
    struct sit_net *sitn = net_generic(net, sit_net_id);
    
    memcpy(dev->dev_addr, &t->parms.iph.saddr, 4);
    memcpy(dev->broadcast, &t->parms.iph.daddr, 4);
    
    if ((__force u16)t->parms.i_flags & SIT_ISATAP)
        dev->priv_flags |= IFF_ISATAP;
    dev->rtnl_link_ops = &sit_link_ops;
    
    err = register_netdevice(dev);
    if (err < 0) goto out;
    
    ipip6_tunnel_clone_6rd(dev, sitn);
    dev_hold(dev);
    ipip6_tunnel_link(sitn, t);

在以上提到的sit_init_net命名空间初始化函数中,sit_net初始化了4个tunnels成员,其中索引0为fallback隧道使用,另外,索引1,2,3分别对应local、remote、local&remote三种隧道。

在ip命令中同时指定了local和remote参数,所以,函数__ipip6_bucket计算得到的prio为3,即一级索引为3,二级索引h的范围为:[0,15],参加宏定义HASH。

static void ipip6_tunnel_link(struct sit_net *sitn, struct ip_tunnel *t)
{
    struct ip_tunnel __rcu **tp = ipip6_bucket(sitn, t);

    rcu_assign_pointer(t->next, rtnl_dereference(*tp));
    rcu_assign_pointer(*tp, t);
}
static struct ip_tunnel __rcu **__ipip6_bucket(struct sit_net *sitn,
        struct ip_tunnel_parm *parms)
{
    __be32 remote = parms->iph.daddr;
    __be32 local = parms->iph.saddr;
    unsigned int h = 0;
    int prio = 0;

    if (remote) {
        prio |= 2;
        h ^= HASH(remote);
    }
    if (local) {
        prio |= 1;
        h ^= HASH(local);
    }
    return &sitn->tunnels[prio][h];
}
#define IP6_SIT_HASH_SIZE  16
#define HASH(addr) (((__force u32)addr^((__force u32)addr>>4))&0xF)

顺便看一下数据平面的隧道查找函数ipip6_tunnel_lookup,其与以上的插入函数正好相反。查找先从高优先级的本地和远端都存在的bucket开始,依次为remote,之后是local,最后是fallback桶。

选择的隧道接口必须是UP状态。

static struct ip_tunnel *ipip6_tunnel_lookup(struct net *net,
        struct net_device *dev, __be32 remote, __be32 local, int sifindex)
{   
    unsigned int h0 = HASH(remote);
    unsigned int h1 = HASH(local);
    struct ip_tunnel *t; 
    struct sit_net *sitn = net_generic(net, sit_net_id);
    int ifindex = dev ? dev->ifindex : 0;
    
    for_each_ip_tunnel_rcu(t, sitn->tunnels_r_l[h0 ^ h1]) {
        if (local == t->parms.iph.saddr && remote == t->parms.iph.daddr &&
            (!dev || !t->parms.link || ifindex == t->parms.link ||
             sifindex == t->parms.link) && (t->dev->flags & IFF_UP))
            return t;
    }
    for_each_ip_tunnel_rcu(t, sitn->tunnels_r[h0]) {
        if (remote == t->parms.iph.daddr &&
            (!dev || !t->parms.link || ifindex == t->parms.link ||
             sifindex == t->parms.link) && (t->dev->flags & IFF_UP))
            return t;
    }
    for_each_ip_tunnel_rcu(t, sitn->tunnels_l[h1]) {
        if (local == t->parms.iph.saddr &&
            (!dev || !t->parms.link || ifindex == t->parms.link ||
             sifindex == t->parms.link) && (t->dev->flags & IFF_UP))
            return t;
    } 
    t = rcu_dereference(sitn->tunnels_wc[0]);
    if (t && (t->dev->flags & IFF_UP))
        return t;
    return NULL;

数据接收

首先查找是否有相应的隧道,没有找到直接返回1。否则,检测隧道配置的协议号,要求protocol必须是IPPROTO_IPV6(41)或者为0。以为sit支持还支持IPv4-over-IPv4和MPLS-over-IPv4,所以要做此检测。

static int ipip6_rcv(struct sk_buff *skb)
{
    const struct iphdr *iph = ip_hdr(skb);
    struct ip_tunnel *tunnel;

    sifindex = netif_is_l3_master(skb->dev) ? IPCB(skb)->iif : 0;
    tunnel = ipip6_tunnel_lookup(dev_net(skb->dev), skb->dev, iph->saddr, iph->daddr, sifindex);
    if (tunnel) {
        struct pcpu_sw_netstats *tstats;

        if (tunnel->parms.iph.protocol != IPPROTO_IPV6 &&
            tunnel->parms.iph.protocol != 0)
            goto out;

        skb->mac_header = skb->network_header;
        skb_reset_network_header(skb);
        IPCB(skb)->flags = 0;
        skb->dev = tunnel->dev;

将当前的IPv4网络头部位置赋值给MAC头部mac_header,重新将网络头部指向IPv6头部。之后,检测是否为欺骗报文。

由于传入的hdr_len参数为0,函数iptunnel_pull_header并不会改变skb的数据区,而是执行更新skb->protocol字段为ETH_P_IPV6,报文清洗,使其像是刚从物理口上接口的报文。由于隧道设备没有GSO功能,对于GSO数据,如果skb被克隆过,执行unclone操作,并清除所有的GSO标志。

        if (packet_is_spoofed(skb, iph, tunnel)) {
            tunnel->dev->stats.rx_errors++;
            goto out;
        }
        if (iptunnel_pull_header(skb, 0, htons(ETH_P_IPV6),
            !net_eq(tunnel->net, dev_net(tunnel->dev))))
            goto out;

        /* skb can be uncloned in iptunnel_pull_header, so old iph is no longer valid
         */
        iph = (const struct iphdr *)skb_mac_header(skb);
        err = IP_ECN_decapsulate(iph, skb);
        if (unlikely(err)) {
            if (log_ecn_error)
                net_info_ratelimited("non-ECT from %pI4 with TOS=%#x\n", &iph->saddr, iph->tos);
            if (err > 1) {
                ++tunnel->dev->stats.rx_frame_errors;
                ++tunnel->dev->stats.rx_errors;
                goto out;
            }
        }
        ...
        netif_rx(skb);
        return 0;
    }
    /* no tunnel matched,  let upstream know, ipsec may handle it */
    return 1;

参加隧道设备初始化函数ipip6_tunnel_bind_dev,如果为隧道指定了目的地址(remote),内核将增加设备标志IFF_POINTOPOINT,对于非ISATAP隧道,packet_is_spoofed总是返回false。

以下函数中的地址欺骗检测主要是对于isatap和6rd隧道的实现。

static bool packet_is_spoofed(struct sk_buff *skb,
                  const struct iphdr *iph, struct ip_tunnel *tunnel)
{
    const struct ipv6hdr *ipv6h;

    if (tunnel->dev->priv_flags & IFF_ISATAP) {
        if (!isatap_chksrc(skb, iph, tunnel))
            return true;
        return false;
    }
    if (tunnel->dev->flags & IFF_POINTOPOINT)
        return false;

    ipv6h = ipv6_hdr(skb);

    if (unlikely(is_spoofed_6rd(tunnel, iph->saddr, &ipv6h->saddr))) {
        net_warn_ratelimited("Src spoofed %pI4/%pI6c -> %pI4/%pI6c\n",
                     &iph->saddr, &ipv6h->saddr, &iph->daddr, &ipv6h->daddr);
        return true;
    }
    if (likely(!is_spoofed_6rd(tunnel, iph->daddr, &ipv6h->daddr)))
        return false;
    if (only_dnatted(tunnel, &ipv6h->daddr))
        return false;
    net_warn_ratelimited("Dst spoofed %pI4/%pI6c -> %pI4/%pI6c\n",
                 &iph->saddr, &ipv6h->saddr, &iph->daddr, &ipv6h->daddr);
    return true;

数据发送

对于IPv6-over-IPv4类型的sit隧道,实际使用的是ipip6_tunnel_xmit发送函数。进入隧道待发送的报文为IPv6协议报文。

static netdev_tx_t sit_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
{
    if (!pskb_inet_may_pull(skb))
        goto tx_err;

    switch (skb->protocol) {
    case htons(ETH_P_IP):
        sit_tunnel_xmit__(skb, dev, IPPROTO_IPIP);
        break;
    case htons(ETH_P_IPV6):
        ipip6_tunnel_xmit(skb, dev);
        break;

以下忽略了isatap和6rd的处理。对于没有指定目标地址(remote)的隧道,skb中必须存在路由缓存,并且路由缓存对应的邻居表现必须存在,即下一跳地址必须存在邻居表中。以下将使用此IPv6地址来获得对应的IPv4地址,如果此IPv6地址为全零地址,将使用报文中的IPv6目的地址。

    |          80 bits        |  16bits   |     32 bits    |
    |-------------------------|-----------|----------------|
    |         0000…0000       |   0000    |  IPv4 address  |
    |-------------------------|-----------|----------------|

如果最终使用的地址不是兼容IPv4地址的IPv6地址,返回错误;否者,由IPv6地址中取出IPv4值,作为隧道的目的地址。IPv6兼容IPv4地址格式如上所示。

static netdev_tx_t ipip6_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
{
    struct ip_tunnel *tunnel = netdev_priv(dev);
    const struct iphdr  *tiph = &tunnel->parms.iph;
    const struct ipv6hdr *iph6 = ipv6_hdr(skb);
    u8 tos = tunnel->parms.iph.tos;
    __be16 df = tiph->frag_off;
    __be32 dst = tiph->daddr;
    u8 protocol = IPPROTO_IPV6;
    int t_hlen = tunnel->hlen + sizeof(struct iphdr);

    if (tos == 1) tos = ipv6_get_dsfield(iph6);

    /* ISATAP (RFC4214) - must come before 6to4 */
    ...

    if (!dst) {
        struct neighbour *neigh = NULL;
        bool do_tx_error = false;

        if (skb_dst(skb))
            neigh = dst_neigh_lookup(skb_dst(skb), &iph6->daddr);
        if (!neigh) {
            net_dbg_ratelimited("nexthop == NULL\n");
            goto tx_error;
        }
        addr6 = (const struct in6_addr *)&neigh->primary_key;
        addr_type = ipv6_addr_type(addr6);

        if (addr_type == IPV6_ADDR_ANY) {
            addr6 = &ipv6_hdr(skb)->daddr;
            addr_type = ipv6_addr_type(addr6);
        }
        if ((addr_type & IPV6_ADDR_COMPATv4) != 0)
            dst = addr6->s6_addr32[3];
        else
            do_tx_error = true;

        neigh_release(neigh);
        if (do_tx_error) goto tx_error;
    }

之后,检测此隧道是否缓存了路由项,没有的话进行出口路由的查找。要求路由项必须为单播。路由出口设备不能等于隧道自身。

    flowi4_init_output(&fl4, tunnel->parms.link, tunnel->fwmark,
               RT_TOS(tos), RT_SCOPE_UNIVERSE, IPPROTO_IPV6,
               0, dst, tiph->saddr, 0, 0, sock_net_uid(tunnel->net, NULL));

    rt = dst_cache_get_ip4(&tunnel->dst_cache, &fl4.saddr);
    if (!rt) {
        rt = ip_route_output_flow(tunnel->net, &fl4, NULL);
        if (IS_ERR(rt)) {
            dev->stats.tx_carrier_errors++;
            goto tx_error_icmp;
        }
        dst_cache_set_ip4(&tunnel->dst_cache, &rt->dst, fl4.saddr);
    }
    if (rt->rt_type != RTN_UNICAST) {
        ip_rt_put(rt);
        dev->stats.tx_carrier_errors++;
        goto tx_error_icmp;
    }
    tdev = rt->dst.dev;
    if (tdev == dev) {
        ip_rt_put(rt);
        dev->stats.collisions++;
        goto tx_error;
    }

函数iptunnel_handle_offloads用于设置IPXIP4卸载。

如果原始报文设置了禁止分片标志位DF,检测路由出口设备的MTU值,因为接下来要添加隧道头部,这里由MTU中减去隧道头部长度。如果MTU小于IPV4_MIN_MTU(68),返回错误。MTU值最小取IPV6_MIN_MTU(1280),如果小于1280,清除DF位。

如果报文长度大于最终的MTU值,并且没有使用GSO卸载功能,返回code值为ICMPV6_PKT_TOOBIG的icmpv6错误。

    if (iptunnel_handle_offloads(skb, SKB_GSO_IPXIP4)) {
        ip_rt_put(rt);
        goto tx_error;
    }
    if (df) {
        mtu = dst_mtu(&rt->dst) - t_hlen;

        if (mtu < 68) {
            dev->stats.collisions++;
            ip_rt_put(rt);
            goto tx_error;
        }
        if (mtu < IPV6_MIN_MTU) {
            mtu = IPV6_MIN_MTU;
            df = 0;
        }
        if (tunnel->parms.iph.daddr)
            skb_dst_update_pmtu_no_confirm(skb, mtu);

        if (skb->len > mtu && !skb_is_gso(skb)) {
            icmpv6_send(skb, ICMPV6_PKT_TOOBIG, 0, mtu);
            ip_rt_put(rt);
            goto tx_error;
        }
    }

执行到此,隧道看起来一切正常。如果隧道的错误计数不为零,并且距离发送错误的时刻不超过30秒(IPTUNNEL_ERR_TIMEO),递减错误计数;否则,时间超过30秒,将错误计数清零。错误计数在函数ipip6_err中增加。

    if (tunnel->err_count > 0) {
        if (time_before(jiffies, tunnel->err_time + IPTUNNEL_ERR_TIMEO)) {
            tunnel->err_count--;
            dst_link_failure(skb);
        } else
            tunnel->err_count = 0;
    }

以下为操作skb缓存进行准备,包括确保足够的头部空间,可写性检测等。以及获取ttl值、tos值,并检测是否进行foo或者fou类型的UDP封装。最后,有函数iptunnel_xmit执行隧道头赋值,和发送操作。

    /* Okay, now see if we can stuff it in the buffer as-is. */
    max_headroom = LL_RESERVED_SPACE(tdev) + t_hlen;

    if (skb_headroom(skb) < max_headroom || skb_shared(skb) ||
        (skb_cloned(skb) && !skb_clone_writable(skb, 0))) {
        struct sk_buff *new_skb = skb_realloc_headroom(skb, max_headroom);
        if (!new_skb) {
            ip_rt_put(rt);
            dev->stats.tx_dropped++;
            kfree_skb(skb);
            return NETDEV_TX_OK;
        }
        if (skb->sk) skb_set_owner_w(new_skb, skb->sk);
        dev_kfree_skb(skb);
        skb = new_skb;
        iph6 = ipv6_hdr(skb);
    }
    ttl = tiph->ttl;
    if (ttl == 0)
        ttl = iph6->hop_limit;
    tos = INET_ECN_encapsulate(tos, ipv6_get_dsfield(iph6));

    if (ip_tunnel_encap(skb, tunnel, &protocol, &fl4) < 0) {
        ip_rt_put(rt);
        goto tx_error;
    }
    skb_set_inner_ipproto(skb, IPPROTO_IPV6);

    iptunnel_xmit(NULL, rt, skb, fl4.saddr, fl4.daddr, protocol, tos, ttl,
              df, !net_eq(tunnel->net, dev_net(dev)));
    return NETDEV_TX_OK;

错误处理函数

sit隧道结构注册时,还注册了错误处理函数ipip6_err。

static struct xfrm_tunnel sit_handler __read_mostly = {
    .handler    =   ipip6_rcv,
    .err_handler    =   ipip6_err,
    .priority   =   1,
};

如下函数ipip6_err,如果没有找到对于的隧道,直接返回。对于code为ICMP_FRAG_NEEDED的情况,更新路径MTU值。最后,更新错误计数和时间戳。

static int ipip6_err(struct sk_buff *skb, u32 info)
{
    const struct iphdr *iph = (const struct iphdr *)skb->data;
    const int type = icmp_hdr(skb)->type;
    const int code = icmp_hdr(skb)->code;
    ...
    
    sifindex = netif_is_l3_master(skb->dev) ? IPCB(skb)->iif : 0;
    t = ipip6_tunnel_lookup(dev_net(skb->dev), skb->dev,
                iph->daddr, iph->saddr, sifindex);
    if (!t) goto out;
    
    if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) {
        ipv4_update_pmtu(skb, dev_net(skb->dev), info, t->parms.link, iph->protocol);
        err = 0;
        goto out;
    }   
    if (type == ICMP_REDIRECT) {
        ipv4_redirect(skb, dev_net(skb->dev), t->parms.link, iph->protocol);
        err = 0;
        goto out;
    }
    err = 0;
    if (__in6_dev_get(skb->dev) && !ip6_err_gen_icmpv6_unreach(skb, iph->ihl * 4, type, data_len))
        goto out;
    
    if (t->parms.iph.daddr == 0)
        goto out;
    if (t->parms.iph.ttl == 0 && type == ICMP_TIME_EXCEEDED)
        goto out;
        
    if (time_before(jiffies, t->err_time + IPTUNNEL_ERR_TIMEO))
        t->err_count++;
    else
        t->err_count = 1;
    t->err_time = jiffies;

另外,在接收函数tunnel64_rcv中,如果没有找到注册的隧道处理结构(包括sit),使用xfrm4_tunnel_register函数注册。那么将返回code为ICMP_PORT_UNREACH的错误,在实际中遇到此错误,会有些不知所措。

static int tunnel64_rcv(struct sk_buff *skb)
{                                   
    struct xfrm_tunnel *handler;    
   
    if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
        goto drop;
   
    for_each_tunnel_rcu(tunnel64_handlers, handler)
        if (!handler->handler(skb)) 
            return 0;               
   
    icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);   

IPv6地址配置

对于sit隧道设备,不使用IPv6隐私地址。

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

    INIT_LIST_HEAD(&ndev->tempaddr_list);
    ndev->desync_factor = U32_MAX;
    if ((dev->flags&IFF_LOOPBACK) ||
        dev->type == ARPHRD_TUNNEL ||
        dev->type == ARPHRD_TUNNEL6 ||
        dev->type == ARPHRD_SIT ||
        dev->type == ARPHRD_NONE) {
        ndev->cnf.use_tempaddr = -1;
    }

如果sit设备地址配置发生变化,或者接口up/down等,都会出发一下通知函数addrconf_notify。

static int addrconf_notify(struct notifier_block *this, unsigned long event, void *ptr)
{
    ...
        switch (dev->type) {
#if IS_ENABLED(CONFIG_IPV6_SIT)
        case ARPHRD_SIT:
            addrconf_sit_config(dev);
            break;
#endif

在函数addrconf_sit_config中,核心处理函数为sit_add_v4_addrs。这里先不涉及ISATAP隧道处理。

static void addrconf_sit_config(struct net_device *dev)
{
    struct inet6_dev *idev;

    ASSERT_RTNL();
    /*
     * Configure the tunnel with one of our IPv4 addresses... 
     * we should configure all of our v4 addrs in the tunnel
     */
    idev = ipv6_find_idev(dev);
    if (IS_ERR(idev)) {
        pr_debug("%s: add_dev failed\n", __func__);
        return;
    }
    if (dev->priv_flags & IFF_ISATAP) {
        addrconf_addr_gen(idev, false);
        return;
    }
    sit_add_v4_addrs(idev);

    if (dev->flags&IFF_POINTOPOINT)
        addrconf_add_mroute(dev);

对于sit隧道设备,其设备地址dev_addr为隧道的(local)本地IP地址,如192.168.20.5。对于POINTOPOINT设备,使用IPv6本地链路前缀0xfe800000。否则,其它类型使用兼容IPv4地址的IPv6地址格式,前缀长度为96,全部为零。

static void sit_add_v4_addrs(struct inet6_dev *idev)
{
    struct in6_addr addr;
    struct net_device *dev;
    struct net *net = dev_net(idev->dev);
    int scope, plen;
    u32 pflags = 0;

    ASSERT_RTNL();

    memset(&addr, 0, sizeof(struct in6_addr));
    memcpy(&addr.s6_addr32[3], idev->dev->dev_addr, 4);

    if (idev->dev->flags&IFF_POINTOPOINT) {
        addr.s6_addr32[0] = htonl(0xfe800000);
        scope = IFA_LINK;
        plen = 64;
    } else {
        scope = IPV6_ADDR_COMPATv4;
        plen = 96;
        pflags |= RTF_NONEXTHOP;
    }

如果IPv6地址的最后4个字节不为空,创建相应的隧道接口地址和路由项,返回结束处理。否则,遍历隧道设备所在命名空间中的所有接口,对于每个UP状态的接口,遍历其所有的地址:
1)这里不考虑本地链路地址(RT_SCOPE_LINK);
2)如果隧道为点到点类型,也不考虑范围scope大于等于RT_SCOPE_HOST的地址;
3)对于RT_SCOPE_UNIVERSE或者RT_SCOPE_SITE地址,将其加上IPv6前缀,配置到隧道接口上。

    if (addr.s6_addr32[3]) {
        add_addr(idev, &addr, plen, scope);
        addrconf_prefix_route(&addr, plen, 0, idev->dev, 0, pflags, GFP_KERNEL);
        return;
    }
    for_each_netdev(net, dev) {
        struct in_device *in_dev = __in_dev_get_rtnl(dev);
        if (in_dev && (dev->flags & IFF_UP)) {
            struct in_ifaddr *ifa;
            int flag = scope;

            in_dev_for_each_ifa_rtnl(ifa, in_dev) {
                addr.s6_addr32[3] = ifa->ifa_local;

                if (ifa->ifa_scope == RT_SCOPE_LINK)
                    continue;
                if (ifa->ifa_scope >= RT_SCOPE_HOST) {
                    if (idev->dev->flags&IFF_POINTOPOINT)
                        continue;
                    flag |= IFA_HOST;
                }
                add_addr(idev, &addr, plen, flag);
                addrconf_prefix_route(&addr, plen, 0, idev->dev, 0, pflags, GFP_KERNEL);

如下fallback设备sit0,其不是点到点设备,前缀使用的为全零,包括lo、ens33、ens34接口的IPv4地址,都转换为IPv6地址,配置在了sit0上。但是,这三个接口上scope为link的地址,并没有配置到sit0上。

而对于点到点隧道设备sit1,其上并没有配置由其它接口IPv4地址生成的IPv6地址。

# ip address
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:0c:29:ea:2e:27 brd ff:ff:ff:ff:ff:ff
    altname enp2s1
    inet 192.168.30.1/24 scope global ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:feea:2e27/64 scope link 
       valid_lft forever preferred_lft forever
3: ens34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 00:0c:29:ea:2e:31 brd ff:ff:ff:ff:ff:ff
    altname enp2s2
    inet 192.168.9.177/24 brd 192.168.9.255 scope global dynamic ens34
       valid_lft 1093sec preferred_lft 1093sec
    inet6 fe80::20c:29ff:feea:2e31/64 scope link 
       valid_lft forever preferred_lft forever
5: sit0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
    link/sit 0.0.0.0 brd 0.0.0.0
    inet6 ::192.168.9.177/96 scope global 
       valid_lft forever preferred_lft forever
    inet6 ::192.168.30.1/96 scope global 
       valid_lft forever preferred_lft forever
    inet6 ::127.0.0.1/96 scope host 
       valid_lft forever preferred_lft forever
6: sit1@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
    link/sit 192.168.20.5 peer 192.168.20.1
    inet6 fe80::c0a8:1405/64 scope link 
       valid_lft forever preferred_lft forever

对于路由而言,前缀长度为96的地址,都经过sit0隧道设备。

# ip -6 router
::1 dev lo proto kernel metric 256 pref medium
::/96 dev sit0 proto kernel metric 256 pref medium
fe80::/64 dev sit1 proto kernel metric 256 pref medium

在以上函数addrconf_sit_config的最后,将调用addrconf_add_mroute为点到点隧道增加多播路由。

static void addrconf_add_mroute(struct net_device *dev)
{      
    struct fib6_config cfg = {
        .fc_table = l3mdev_fib_table(dev) ? : RT6_TABLE_LOCAL,
        .fc_metric = IP6_RT_PRIO_ADDRCONF,
        .fc_ifindex = dev->ifindex,
        .fc_dst_len = 8,
        .fc_flags = RTF_UP,
        .fc_type = RTN_UNICAST,
        .fc_nlinfo.nl_net = dev_net(dev),
    };
   
    ipv6_addr_set(&cfg.fc_dst, htonl(0xFF000000), 0, 0, 0);

    ip6_route_add(&cfg, GFP_KERNEL, NULL);

如下的路由项,如果隧道设备不是l3mdev子设备,多播路由项将位于路由表RT6_TABLE_LOCAL(255)中。

# ip -6 route show table 255
multicast ff00::/8 dev sit1 proto kernel metric 256 pref medium

内核版本 5.10

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值