关键在封装并发出了帧-IP冲突也无所谓

来源:互联网 发布:十三经注疏 知乎 编辑:程序博客网 时间:2024/04/30 12:07
最近有点走火入魔了!本文所用技术非标准,较真儿者慎入!!
一个局域网内,两台机器拥有同样的IP,可以吗?
这不就是IP地址冲突吗?当然不行!
可是要知道,如果搞点旁门左道,还是可以做到的!
首先要明白的是,IP数据报在以太网中的收发特征:
对于发送来讲:只要你有一个目标MAC供你封装成帧,就可以发出去,而这个MAC地址是由ARP来获取的;
对于接收来讲:只要收到帧的目标MAC是接收到帧的网卡的MAC地址,就可以正确接收!

现在我们逐步的来实现一个局域网内拥有同样IP还能正常越过默认网关访问不同外网的情景。实际上,在我实现的简版SDN中,一切都是保存在conntrack中的,它甚至可以保存一个流的上一跳和下一跳neighbour,这就意味着完全架空了本机的路由逻辑和arp逻辑!使本机完全退化成了一个switch!专注于数据包的转发!neighbour的定义是次要的,完全没有必要照抄Linux内核的neighbour结构体,它的实质就是一个MAC地址而已,思路很简单:
1.流头进来在正方向的PREROUTING中将其源MAC地址保存在conntrack;
2.属于同一流的数据包在反方向的POSTROUTING中获取conntrack中的MAC地址封装为目标MAC;
3.发送并返回STOLEN。

就是这么简单!实际上如果能在Linux中实现Policy ARP就好了,也就是说,可以为同一个IP地址映射多条ARP项,每个项有不同的MAC地址。毕竟neighbour是和dst_entry即路由表项相关联的,因此只要保证同一个IP地址的多个ARP映射属于不同的路由表项即可,而这很容易通过IPMARK+Policy Routing来实现!
        不幸的是,Linux并没有实现如此让人走火入魔的Policy ARP,现有的隔离措施就是使用NET命名空间,然而它是用于虚拟化的,同一个网卡只能属于同一个命名空间,此场景不适用!那么怎么办?只能自己写Netfilter代码了啊,还好,由于这个逻辑超级清晰,因此所做的修改也是很容易的,实现了这个的话,仰天长啸,嘲笑一下地址冲突!
        保存源MAC地址在ipv4_conntrack_in的最后来做,而还原保存的MAC地址到目标MAC地址的操作在ipv4_confirm中来做。代码如下:
//继续偷梁换柱借用nat的extendstruct nf_conn_nat {    //自动保存回复帧的目标MAC地址    unsigned char reply_gw_mac[ETH_ALEN];    //自动保存本机MAC地址    unsigned char orig_gw_mac[ETH_ALEN];    //自动保存设备    struct net_device *dev;};static unsigned int ipv4_conntrack_in(unsigned int hooknum,                                      struct sk_buff *skb,                                      const struct net_device *in,                                      const struct net_device *out,                                      int (*okfn)(struct sk_buff *)){                unsigned int ret = nf_conntrack_in(dev_net(in), PF_INET, hooknum, skb);                if (ret == NF_ACCEPT) {                enum ip_conntrack_info ctinfo;                struct nf_conn *ct;                struct nf_conn_nat *automac;                ct = nf_ct_get(skb, &ctinfo);                if (!ct) {                        goto out;                }                if (skb->dev->flags & IFF_LOOPBACK)                        return ret;                if (CTINFO2DIR(ctinfo) != IP_CT_DIR_ORIGINAL) {                        goto out;                }                automac = nf_ct_ext_find(ct, NF_CT_EXT_NAT);                if (automac == NULL) {                        automac = nf_ct_ext_add(ct, NF_CT_EXT_NAT, GFP_ATOMIC);                        if (automac == NULL) {                                goto out;                        }                        struct ethhdr *eth = (struct ethhdr*)(skb->data - ETH_HLEN);                        memcpy(automac->reply_gw_mac, eth->h_source, ETH_ALEN);                        memcpy(automac->orig_gw_mac, eth->h_dest, ETH_ALEN);                        automac->dev = skb->dev;                }        }out:        return ret;;}static unsigned int ipv4_confirm(unsigned int hooknum,                                 struct sk_buff *skb,                                 const struct net_device *in,                                 const struct net_device *out,                                 int (*okfn)(struct sk_buff *)){               .............out:        /* We've seen it coming out the other side: confirm it */        ret = nf_conntrack_confirm(skb);        if (ret == NF_ACCEPT) {                struct nf_conn_nat *automac = NULL;                struct net_device *dev = NULL;                unsigned int hh_len = 0;                if (skb->dev->flags & IFF_LOOPBACK)                        return ret;                if (CTINFO2DIR(ctinfo) == IP_CT_DIR_ORIGINAL)                        return ret;                if (hooknum != NF_INET_POST_ROUTING)                        return ret;                automac = nf_ct_ext_find(ct, NF_CT_EXT_NAT);                if (automac == NULL)                        return ret;                dev = automac->dev;                skb->dev = dev;                hh_len = LL_RESERVED_SPACE(dev);                if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) {                        struct sk_buff *skb2;                        skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));                        if (skb2 == NULL) {                                kfree_skb(skb);                                return -ENOMEM;                        }                        if (skb->sk)                                skb_set_owner_w(skb2, skb->sk);                        kfree_skb(skb);                        skb = skb2;                }                //填充以太帧头,注意偏移!                char *header = skb->data - hh_len;                header[0] = 0x0;                header[1] = 0x0;                struct ethhdr *eth = (struct ethhdr*)(header+2);                memcpy(eth->h_dest, automac->reply_gw_mac, ETH_ALEN);                memcpy(eth->h_source, automac->orig_gw_mac, ETH_ALEN);                eth->h_proto = htons(ETH_P_IP);                skb_push(skb, ETH_HLEN);                skb->_skb_dst = NULL;                dev_queue_xmit(skb);                return NF_STOLEN;        }        return ret;}

精妙之处在于,只要有一个包到达该BOX,其源MAC地址就能被conntrack记住,当反方向的包到来并要发回去的时候,就能取出该源MAC地址作为目标MAC地址封装到以太帧中,然后无需经由ARP层直接发送出去。现在的问题是,两个相同IP地址的主机可以同时发包过来吗?答案无疑是肯定的,因为它们俩发包前解析BOX的MAC地址,该BOX无疑全部回复,它们总有个先来后到,唯一的副作用时后到的那个ARP请求会更新BOX的ARP表,将相同的那个IP的MAC地址覆盖成自己的MAC地址,然而这无所谓了,因为既然两台主机都得到了BOX的MAC地址,数据包肯定能发出去到达BOX,返回包在寻址这两台机器的时候,并不通过ARP来寻址,而是通过这两台机器自己携带过去的源MAC地址来作为目标MAC的,故而覆盖也无所谓了!
        只要保证没有五元组的冲突即可,而这种冲突的可能性是极其小的。
        严格来讲ARP属于IP的下层,是和链路层接口的协议,它是”下一跳解析协议“的一种,适用于IPv4到以太网的适配,ARP只是动态的帮你完成了映射,如果你知道该如何封装以太帧,完全可以不用ARP,要么手工设置static的映射,要么就是从某处得到一个映射,关键点不在ARP或者映射,关键点在于你能得到一个MAC地址!只要你能得到目标MAC地址,那么封装以太帧即可,此时是不关心源MAC的!
        我真的走火入魔了,不管是用MAC桥微端口搞定了OpenVPN的源地址选择,还是构造假网关在配置Windows路由时指定源地址,还是本文说的IP地址冲突情景下的数据通信,都不是标准的做法,希望较真的同学注意。如果想靠这个理解网络的行为,那是再好不过了...
原创粉丝点击