6rd隧道处理

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

基础的sit隧道处理请参考:SIT通用隧道,此处仅涉及6rd相关部分。

命名空间fallback设备

命名空间初始化中,使用函数ipip6_tunnel_clone_6rd初始化fallback设备的6rd相关参数。

static int __net_init sit_init_net(struct net *net)
{
    ...
    /* FB netdevice is special: we have one, and only one per netns.
     * Allowing to move it to another netns is clearly unsafe.
     */
    sitn->fb_tunnel_dev->features |= NETIF_F_NETNS_LOCAL;

    err = register_netdev(sitn->fb_tunnel_dev);
    if (err)
        goto err_reg_dev;

    ipip6_tunnel_clone_6rd(sitn->fb_tunnel_dev, sitn);
    ipip6_fb_tunnel_init(sitn->fb_tunnel_dev);

6rd要求的四个参数:6rdprefix和prefixlen设置为0x2002::0/16(6to4的前缀),relay_prefix和relay_prefixlen设置为::0/0。
fallback隧道设备

static void ipip6_tunnel_clone_6rd(struct net_device *dev, struct sit_net *sitn)
{
#ifdef CONFIG_IPV6_SIT_6RD
    struct ip_tunnel *t = netdev_priv(dev);

    if (dev == sitn->fb_tunnel_dev || !sitn->fb_tunnel_dev) {
        ipv6_addr_set(&t->ip6rd.prefix, htonl(0x20020000), 0, 0, 0);
        t->ip6rd.relay_prefix = 0;
        t->ip6rd.prefixlen = 16;
        t->ip6rd.relay_prefixlen = 0;
    } else {
        struct ip_tunnel *t0 = netdev_priv(sitn->fb_tunnel_dev);
        memcpy(&t->ip6rd, &t0->ip6rd, sizeof(t->ip6rd));
    }
#endif

netlink用户接口

# ip tunnel add 6rd-vif mode sit local 192.168.3.47
# ip tunnel 6rd dev 6rd-vif 6rd-prefix 2001:db8::/32 6rd-relay_prefix 192.168.3.49/32

函数ipip6_newlink创建新的sit隧道,函数ipip6_netlink_6rd_parms检测6rd参数,如果合法,由函数ipip6_tunnel_update_6rd进行更新。

static int ipip6_newlink(struct net *src_net, struct net_device *dev,
             struct nlattr *tb[], struct nlattr *data[], struct netlink_ext_ack *extack)
{
    ...
    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;
    }
#ifdef CONFIG_IPV6_SIT_6RD
    if (ipip6_netlink_6rd_parms(data, &ip6rd))
        err = ipip6_tunnel_update_6rd(nt, &ip6rd);
#endif

如同fallback设备,新的6rd-sit隧道设备创建时,ipip6_tunnel_clone_6rd函数会将fb_tunnel_dev的默认6rd参数,配置到新的隧道设备之上。

static int ipip6_tunnel_create(struct net_device *dev)
{   
    ...
    dev->rtnl_link_ops = &sit_link_ops;
    
    err = register_netdevice(dev);
    if (err < 0) goto out;
    
    ipip6_tunnel_clone_6rd(dev, sitn);

如下更新6rd参数,获取命令行配置的6rdprefix、6rdprefixlen、relay_prefix(32位IPv4地址)和relay_prefixlen。

/* This function returns true when 6RD attributes are present in the nl msg */
static bool ipip6_netlink_6rd_parms(struct nlattr *data[], struct ip_tunnel_6rd *ip6rd)
{
    bool ret = false;
    memset(ip6rd, 0, sizeof(*ip6rd));

    if (!data)
        return ret;

    if (data[IFLA_IPTUN_6RD_PREFIX]) {
        ret = true;
        ip6rd->prefix = nla_get_in6_addr(data[IFLA_IPTUN_6RD_PREFIX]);
    }

    if (data[IFLA_IPTUN_6RD_RELAY_PREFIX]) {
        ret = true;
        ip6rd->relay_prefix =
            nla_get_be32(data[IFLA_IPTUN_6RD_RELAY_PREFIX]);
    }

    if (data[IFLA_IPTUN_6RD_PREFIXLEN]) {
        ret = true;
        ip6rd->prefixlen = nla_get_u16(data[IFLA_IPTUN_6RD_PREFIXLEN]);
    }

    if (data[IFLA_IPTUN_6RD_RELAY_PREFIXLEN]) {
        ret = true;
        ip6rd->relay_prefixlen =
            nla_get_u16(data[IFLA_IPTUN_6RD_RELAY_PREFIXLEN]);
    }

首先,6rd中继路由器的prefixlen不能大于32比特(IPv4地址);另外,6rd自身的prefixlen加上须追加到前缀中的IPv4地址长度之和,不能大于64(留作接口ID)。IPv4地址长度减去公用的前缀长度relay_prefixlen,可得追加到6rdprefix中的长度。

之后,检测prefix和prefixlen配置的合法性,这里,如果prefix中超出prefixlen的部分,如果不是全零,将会报错。对于中继路由器的IPv4类型的relay_prefix和relay_prefixlen也做类似的合法性检测。最后,赋值到ip_tunnel_6rd隧道结构中。

函数netdev_state_change发送NETDEV_CHANGE通知链消息,并向上传发送RTM_NEWLINK的netlink消息。

static int ipip6_tunnel_update_6rd(struct ip_tunnel *t, struct ip_tunnel_6rd *ip6rd)
{
    struct in6_addr prefix;
    __be32 relay_prefix;

    if (ip6rd->relay_prefixlen > 32 ||
        ip6rd->prefixlen + (32 - ip6rd->relay_prefixlen) > 64)
        return -EINVAL;

    ipv6_addr_prefix(&prefix, &ip6rd->prefix, ip6rd->prefixlen);
    if (!ipv6_addr_equal(&prefix, &ip6rd->prefix))
        return -EINVAL;
    if (ip6rd->relay_prefixlen)
        relay_prefix = ip6rd->relay_prefix &
                   htonl(0xffffffffUL << (32 - ip6rd->relay_prefixlen));
    else
        relay_prefix = 0;
    if (relay_prefix != ip6rd->relay_prefix)
        return -EINVAL;

    t->ip6rd.prefix = prefix;
    t->ip6rd.relay_prefix = relay_prefix;
    t->ip6rd.prefixlen = ip6rd->prefixlen;
    t->ip6rd.relay_prefixlen = ip6rd->relay_prefixlen;
    dst_cache_reset(&t->dst_cache);
    netdev_state_change(t->dev);

数据接收

在接收函数中,进行欺骗报文检测,函数packet_is_spoofed实现。

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;

        if (packet_is_spoofed(skb, iph, tunnel)) {
            tunnel->dev->stats.rx_errors++;
            goto out;
        }

对于6rd隧道,主要是函数is_spoofed_6rd的两次调用。分别检测封装头的IPv4源地址是否嵌入在内部IPv6头部的IPv6源地址中,以及封装头的IPv4目的地址是否嵌入在内部IPv6头部的IPv6目的地址中。

首先检测的为6rd的源地址,如果失败则认定为欺骗报文。其次,检测6rd目的地址,如果没有通过,需要再次确定失败的原因是否是进行了DNAT导致的,如果没有经过DNAT,才认为目的地址为欺骗地址。

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;

函数is_spoofed_6rd调用check_6rd,检测回填的参数v4embed是否等于报文头部的IPv4地址v4addr。

static inline bool is_spoofed_6rd(struct ip_tunnel *tunnel, const __be32 v4addr, const struct in6_addr *v6addr)
{
    __be32 v4embed = 0;
    if (check_6rd(tunnel, v6addr, &v4embed) && v4addr != v4embed)
        return true;
    return false;
}

如果传入的IPv6地址与6rd隧道配置的前缀/前缀长度相等,取出IPv6地址中嵌入的IPv4地址,赋值给参数v4dst,返回true。由于IPv4地址嵌入在IPv6的前缀中,并且嵌入位数等于(32-relay_prefixlen)。最终的IPv4地址等于relay_prefix或上取出的值(htonl(d))。

IPv6地址保存在长度为4的32bit数组中,首先prefixlen除以32(>>5)找到数组索引值pbw0,但是prefixlen不一定能被32整除,余数保存在pbi0中,pbi0表示的位数位于下一个数组成员中。

relay_prefixlen等于32表示IPv6地址中没有IPv4地址,d=0。 否则,先取出pbw0数组索引中的值,在左移pbi0比特位,留出剩余的IPv4比特位,之后右移relay_prefixlen,将前缀清空,前缀保存在隧道的ip6rd.relay_prefix中,最后或上即可。

之后,如果pbi0大于relay_prefixlen,表明在下一个数组中,还有需要取出的IPv4地址位,长度为pbi1,将这些位或到d中。

对于6to4隧道,IPv4地址位于IPv6地址中的固定位置。

/* If the IPv6 address comes from 6rd / 6to4 (RFC 3056) addr space this function
 * stores the embedded IPv4 address in v4dst and returns true.
 */
static bool check_6rd(struct ip_tunnel *tunnel, const struct in6_addr *v6dst, __be32 *v4dst)
{
#ifdef CONFIG_IPV6_SIT_6RD
    if (ipv6_prefix_equal(v6dst, &tunnel->ip6rd.prefix, tunnel->ip6rd.prefixlen)) {
        unsigned int pbw0, pbi0;
        int pbi1;
        u32 d;

        pbw0 = tunnel->ip6rd.prefixlen >> 5;
        pbi0 = tunnel->ip6rd.prefixlen & 0x1f;

        d = tunnel->ip6rd.relay_prefixlen < 32 ?
            (ntohl(v6dst->s6_addr32[pbw0]) << pbi0) >>
            tunnel->ip6rd.relay_prefixlen : 0;

        pbi1 = pbi0 - tunnel->ip6rd.relay_prefixlen;
        if (pbi1 > 0)
            d |= ntohl(v6dst->s6_addr32[pbw0 + 1]) >> (32 - pbi1);

        *v4dst = tunnel->ip6rd.relay_prefix | htonl(d);
        return true;
    }
#else
    if (v6dst->s6_addr16[0] == htons(0x2002)) {
        /* 6to4 v6 addr has 16 bits prefix, 32 v4addr, 16 SLA, ... */
        memcpy(v4dst, &v6dst->s6_addr16[1], 4);
        return true;
    }
#endif
    return false;
}

以下only_dnatted首先计算隧道接口上配置的IPv6地址的前缀长度。对于6rd隧道,其原本的prefixlen,加上嵌入在其中的IPv4地址部分(32-relay_prefixlen),即IPv6代理地址前缀长度。对于6to4隧道,前缀长度等于固定值(0x2002)长度16+32bit的IPv4地址,等于48位。

之后,将此前缀长度和报文的IPv6地址,与隧道接口上的IPv6地址进行比较,如果为真,认为报文的目的地址没有被伪造。

/* Checks if an address matches an address on the tunnel interface.
 * Used to detect the NAT of proto 41 packets and let them pass spoofing test.
 * Long story:
 * This function is called after we considered the packet as spoofed in is_spoofed_6rd.
 * We may have a router that is doing NAT for proto 41 packets
 * for an internal station. Destination a.a.a.a/PREFIX:bbbb:bbbb
 * will be translated to n.n.n.n/PREFIX:bbbb:bbbb. And is_spoofed_6rd
 * function will return true, dropping the packet.
 * But, we can still check if is spoofed against the IP addresses associated with the interface.
 */
static bool only_dnatted(const struct ip_tunnel *tunnel, const struct in6_addr *v6dst)
{
    int prefix_len;

#ifdef CONFIG_IPV6_SIT_6RD
    prefix_len = tunnel->ip6rd.prefixlen + 32
        - tunnel->ip6rd.relay_prefixlen;
#else
    prefix_len = 48;
#endif
    return ipv6_chk_custom_prefix(v6dst, prefix_len, tunnel->dev);
}

数据发送

如下发送函数,如果隧道没有配置目的地址(remote)首先来确定隧道封装的IPv4目的地址,调用函数try_6rd,尝试由报文的IPv6目的地址中,取出6rd格式的IPv4目的地址(或者6to4格式的IPv4地址)。之后尝试由下一跳地址或者IPv6目的地址中,取出IPv4兼容格式的目的地址。

static netdev_tx_t ipip6_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
{
    const struct iphdr  *tiph = &tunnel->parms.iph;
    const struct ipv6hdr *iph6 = ipv6_hdr(skb);
    __be32 dst = tiph->daddr;

    /* ISATAP (RFC4214) - must come before 6to4 */
    if (dev->priv_flags & IFF_ISATAP) {
        ...
    }

    if (!dst)
        dst = try_6rd(tunnel, &iph6->daddr);

    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;
    }

内核版本 5.10

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

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

抵扣说明:

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

余额充值