一个局域网内,两台机器拥有同样的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的extend
struct 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地址冲突情景下的数据通信,都不是标准的做法,希望较真的同学注意。如果想靠这个理解网络的行为,那是再好不过了...
原文链接: https://blog.csdn.net/dog250/article/details/10044475
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;
也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬
![关键在封装并发出了帧-IP冲突也无所谓](https://www.ccppcoding.com/wp-content/themes/justnews/themer/assets/images/lazy.png)
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/408845
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!