现在的位置: 首页 > 综合 > 正文

虚拟网卡与loopback的思想

2016年07月23日 ⁄ 综合 ⁄ 共 2154字 ⁄ 字号 评论关闭

在linux上卸载了loopback网卡设备之后,本地地址全部不通,这是不应该的吗?所有的本地网卡在配置ip地址的时候会调用fib_add_ifaddr函数:
void fib_add_ifaddr(struct in_ifaddr *ifa)
{
...
    fib_magic(RTM_NEWROUTE, RTN_LOCAL, addr, 32, prim);
//如此一来,加入一条路由,所有的目的地址是本网卡地址的数据包将通过loopback发送,这实现在ip_route_output_slow函数中。
...
}
在ip_route_output_slow中有以下的语句:
if (res.type == RTN_LOCAL) {
...
    dev_out = &loopback_dev; //如果loopback被ifdown了,那么数据从何发送呢
...

    goto make_route;
...

}
因此loopback实际上是很重要的,如果你很在意自己的地址的话,比如你在开发一个网络程序,你又只有一台机器,因此你必然需要自己使用自己的ip地址来调试,那么此时loopback就有了用武之地了。之所以需要loopback来支持本机数据的路由是因为真实的网卡只能将数据发送给物理线路或者从物理线路上接收数据,而不能从别的地方接收或者发送数据,这和虚拟网卡是一致的,然而此时我们也明白了loopback和虚拟网卡的本质区别,loopback只是需要把数据环回过来而已,原封不动的环回,所以我们并不需要一个“物理层”,即使协议栈需要一个物理层,我们缺失的物理层并不会带来问题,反观虚拟网卡就不一样了,虚拟网卡的数据并不是原封不动的环回,而是需要被修改之后环回或者从应用层重发,于是就需要实现一个“物理层”,然后将该物理层截断,在断裂处加入我们的修改,也就是挂载一个钩子,该物理层就是虚拟网卡出口到字符设备出口加上字符设备入口到虚拟网卡入口以及字符设备出入口之间的部分,于是虚拟网卡有四个出入口,而loopback仅有两个,就是用户空间应用层访问本机地址的进程,它在网卡层次是没有出口的。
     loopback的数据流是:发送数据->路由->loopback->路由->接收数据;而虚拟网卡有好几种数据流,它们是:(环回方式)发送数据->路由->虚拟网卡send->字符设备read->应用层->字符设备write->虚拟网卡receive->路由->接收数据,(隧道方式1)发送数据->路由->虚拟网卡send->字符设备read->应用层->socket->路由->...(forward),(隧道方式2)...->...,(自组包方式)自己封装物理层数据流->字符设别write->虚拟网卡receive->路由->...总之四个出入口的任何一个地方都能作为数据的起点和终点,换句话说,我们既可以通过虚拟网卡加其字符设备从物理层接收或发送数据,也可以通过套接字加路由从应用层发送或接收数据,因此才如此灵活。
     最后看看虚拟网卡的驱动以及loopback的驱动:先看看loopback的hard_start_xmit函数:
static int loopback_xmit(struct sk_buff *skb, struct net_device *dev)
{
    struct net_device_stats *lb_stats;
    skb_orphan(skb);
    skb->protocol = eth_type_trans(skb,dev);
    skb->dev = dev;
    dev->last_rx = jiffies;
...
    put_cpu();
    netif_rx(skb); //接收
    return(0);
}
可以看出这个xmit函数名字是发送,实质上已经包含了接收逻辑。而虚拟网卡的xmit函数是下面的样子:
static int tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
{
    struct tun_struct *tun = netdev_priv(dev);
    if (!tun->attached)
        goto drop;
...
    skb_queue_tail(&tun->readq, skb); //加入字符设备的读取队列
    dev->trans_start = jiffies;
    if (tun->flags & TUN_FASYNC)
        kill_fasync(&tun->fasync, SIGIO, POLL_IN);
    wake_up_interruptible(&tun->read_wait); //唤醒等待在字符设备上的进程,举个例子,openvpn
    return 0;
drop:
    tun->stats.tx_dropped++;
    kfree_skb(skb);
    return 0;
}
和上面的tun_net_xmit相关联的是字符设备的读写函数,很显然的,write往虚拟网卡中写数据,其实就是直接调用netif_rx_ni(skb),而read则从tun->readq中读取数据,然后直接将数据不经任何处理拷贝入用户空间进程。

抱歉!评论已关闭.