ISATAP隧道处理

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

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

netlink用户接口

函数ipip6_newlink创建新的sit隧道,函数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)
{
    ...
    if (ipip6_tunnel_locate(net, &nt->parms, 0))
        return -EEXIST;

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

对于ISATAP隧道,设置新建设备的IFF_ISATAP私有标志位。

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);
    int err;

    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;

数据接收

在接收函数中,进行欺骗报文检测,由函数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;
        }

对于ISATAP隧道,即设置了设备私有标志IFF_ISATAP的隧道,有函数isatap_chksrc检测欺骗报文。

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;

如果外层IPv4头部的源地址等于配置的PRL(Potential Router List)中的某个地址,确定报文为合法报文,根据PRL地址的标志设置skb的成员ndisc_nodetype。PRL可通过以下命令配置:

# ip tunnel add name isatap0 mode isatap local 192.168.1.100
# 
# ip tunnel prl dev isatap0 prl-default 192.168.1.1 
# ip tunnel prl dev isatap0 prl-nodefault 192.168.1.2
# 
# ip tunnel show 
isatap0: ipv6/ip remote any local 192.168.1.100 pdr 192.168.1.1 pr 192.168.1.2 ttl inherit 6rd-prefix 2002::/16
#
# sudo ip tunnel prl dev isatap0 prl-delete 192.168.1.1

否则,如果源地址不属于PRL,检测内部IPv6头部的源地址是否是ISATAP格式地址,内嵌的IPv4地址是否等于外层IPv4头部的源地址,以及IPv6源地址是否和隧道设备配置的地址的前缀相同,以上条件都成立,则认为是合法报文。

static int isatap_chksrc(struct sk_buff *skb, const struct iphdr *iph, struct ip_tunnel *t)
{
    struct ip_tunnel_prl_entry *p;
    int ok = 1;

    rcu_read_lock();
    p = __ipip6_tunnel_locate_prl(t, iph->saddr);
    if (p) {
        if (p->flags & PRL_DEFAULT)
            skb->ndisc_nodetype = NDISC_NODETYPE_DEFAULT;
        else
            skb->ndisc_nodetype = NDISC_NODETYPE_NODEFAULT;
    } else {
        const struct in6_addr *addr6 = &ipv6_hdr(skb)->saddr;

        if (ipv6_addr_is_isatap(addr6) &&
            (addr6->s6_addr32[3] == iph->saddr) &&
            ipv6_chk_prefix(addr6, t->dev))
            skb->ndisc_nodetype = NDISC_NODETYPE_HOST;
        else
            ok = 0;
    }
    rcu_read_unlock();
    return ok;
}

ISATAP格式的IPv6地址判断函数如下,0x02000000中的第6位(从左开始,基数0)为universal/local位,对于全局IPv4地址,值为1,否则为0。

static inline bool ipv6_addr_is_isatap(const struct in6_addr *addr)
{            
    return (addr->s6_addr32[2] | htonl(0x02000000)) == htonl(0x02005EFE);
} 

数据发送

如下发送函数,对于ISATAP隧道,其处理优先级高于6rd和6to4等隧道,首先,IPv6报文的下一跳地址必须存在于邻居表中;之后,由下一跳地址中取出对应的外部要封装的IPv4目的地址,这就要求下一跳IPv6地址必须是单播地址,而且是ISATAP格式的地址。否则,发生错误。

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) {
        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_UNICAST) &&
             ipv6_addr_is_isatap(addr6))
            dst = addr6->s6_addr32[3];
        else
            do_tx_error = true;

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

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

地址配置

对于ISATAP设备,不发送多播的RS请求。

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)
        return ERR_PTR(err);

    if (dev->flags & (IFF_NOARP | IFF_LOOPBACK))
        ndev->cnf.accept_dad = -1;

#if IS_ENABLED(CONFIG_IPV6_SIT)
    if (dev->type == ARPHRD_SIT && (dev->priv_flags & IFF_ISATAP)) {
        pr_info("%s: Disabled Multicast RS\n", dev->name);
        ndev->cnf.rtr_solicits = 0;
    }
#endif

在生成EUI64格式的接口ID时,对于ISATAP隧道设备,由以下函数__ipv6_isatap_ifid实现。EUI的最后4字节为本地隧道IPv4地址(local),EUI的第一个字节根据地址类型,设置全局或者本地标志位(universal/local),之后的三个字节固定为0x005EFE。

static int addrconf_ifid_sit(u8 *eui, struct net_device *dev)
{
    if (dev->priv_flags & IFF_ISATAP)
        return __ipv6_isatap_ifid(eui, *(__be32 *)dev->dev_addr);
    return -1;
}
static int __ipv6_isatap_ifid(u8 *eui, __be32 addr)
{
    if (addr == 0)
        return -1;
    eui[0] = (ipv4_is_zeronet(addr) || ipv4_is_private_10(addr) ||
          ipv4_is_loopback(addr) || ipv4_is_linklocal_169(addr) ||
          ipv4_is_private_172(addr) || ipv4_is_test_192(addr) ||
          ipv4_is_anycast_6to4(addr) || ipv4_is_private_192(addr) ||
          ipv4_is_test_198(addr) || ipv4_is_multicast(addr) ||
          ipv4_is_lbcast(addr)) ? 0x00 : 0x02;
    eui[1] = 0;
    eui[2] = 0x5E;
    eui[3] = 0xFE;
    memcpy(eui + 4, &addr, 4);
    return 0;
}
static int ipv6_generate_eui64(u8 *eui, struct net_device *dev)
{
    switch (dev->type) {
    case ARPHRD_SIT:
        return addrconf_ifid_sit(eui, dev);

内核版本 5.10

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

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

抵扣说明:

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

余额充值