qemu网络虚拟化之数据流向分析三

2016-09-27

前篇文章通过分析源代码,大致描述了各个数据结构之间的关系是如何建立的,那么今天就从数据包的角度,分析下数据包是如何在这些数据结构中间流转的!



这部分内容需要结合前面两篇文章来看,我们还是按照从Tap设备->Hub->NIC的流程。

首先看Tap设备,在Tap.c文件中:

先看下Tap设备注册的处理函数

1 static NetClientInfo net_tap_info = {
2     .type = NET_CLIENT_OPTIONS_KIND_TAP,
3     .size = sizeof(TAPState),
4     .receive = tap_receive,
5     .receive_raw = tap_receive_raw,
6     .receive_iov = tap_receive_iov,
7     .poll = tap_poll,
8     .cleanup = tap_cleanup,
9 };

其中重点就是tap_receive,该函数中会根据情况调用tap_receive_raw,而tap_receive_iov是在使用IO向量的情况下使用的,逻辑上和tap_receive是一个级别,关于IO向量下面首先会简要分析下:

IO向量:

IO向量的主要目的是让调用在一次原子操作中连续读写多个缓冲区,从而提高效率。IO向量结构如下:

1 struct iovec {
2     void *iov_base;
3     size_t iov_len;
4 };

结构很简单,前者iov_base指向一个缓冲区,iov_len记录缓冲区的长度。一般会有一个iovec数组来描述IO向量,数组的个数就是缓冲区的个数。

先看下tap_receive函数

 1 static ssize_t tap_receive(NetClientState *nc, const uint8_t *buf, size_t size)
 2 {
 3     TAPState *s = DO_UPCAST(TAPState, nc, nc);
 4     struct iovec iov[1];
 5
 6     if (s->host_vnet_hdr_len && !s->using_vnet_hdr) {
 7         return tap_receive_raw(nc, buf, size);
 8     }
 9
10     iov[0].iov_base = (char *)buf;
11     iov[0].iov_len  = size;
12
13     return tap_write_packet(s, iov, 1);
14 }

该函数接收从用户应用程序传递过来的数据,然后写入到设备文件中。在没有使用virtIO 的情况下是直接交给tap_receive_raw处理,否则设置IO向量并调用tap_write_packet函数

 1 static ssize_t tap_write_packet(TAPState *s, const struct iovec *iov, int iovcnt)
 2 {
 3     ssize_t len;
 4
 5     do {
 6         len = writev(s->fd, iov, iovcnt);
 7     } while (len == -1 && errno == EINTR);
 8
 9     if (len == -1 && errno == EAGAIN) {
10         tap_write_poll(s, true);
11         return 0;
12     }
13
14     return len;
15 }

在该函数中调用了writev函数进向TAPState->fd 进行数据的写入。这里数据被组织成IO向量,写入完成需要调用tap_write_poll更新下fd的处理函数(原因)。

而tap_receive_raw函数和tap_receive_iov函数本质上和tap_receive实现类似的功能,只有一些细枝末节的变化,这里就不在分析。

下面还是转换方向,从tap部分的发送函数说起,这里看tap_send函数

 1 static void tap_send(void *opaque)
 2 {
 3     TAPState *s = opaque;
 4     int size;
 5
 6     do {
 7         uint8_t *buf = s->buf;
 8     
 9         size = tap_read_packet(s->fd, s->buf, sizeof(s->buf));
10         if (size <= 0) {
11             break;
12         }
13     //如果设置了vnet头部长度,但是using_vnet_hdr为0,就在移动buffer指针且修正size。因为这里buffer里面包含了头部,但是设备并没有使用
14         if (s->host_vnet_hdr_len && !s->using_vnet_hdr) {
15             buf  += s->host_vnet_hdr_len;
16             size -= s->host_vnet_hdr_len;
17         }
18
19         size = qemu_send_packet_async(&s->nc, buf, size, tap_send_completed);
20         if (size == 0) {
21             tap_read_poll(s, false);
22         }
23     } while (size > 0 && qemu_can_send_packet(&s->nc));
24 }

这里通过一个循环反复调用tap_read_packet函数从打开的设备文件中读取数据,并每一次读取完毕调用qemu_send_packet_async函数进行数据的发送。前者比较简单,就是普通的读文件操作,后者我们来看下

1 size_t qemu_send_packet_async(NetClientState *sender,
2                                const uint8_t *buf, int size,
3                                NetPacketSent *sent_cb)
4 {
5     return qemu_send_packet_async_with_flags(sender, QEMU_NET_PACKET_FLAG_NONE,
6                                              buf, size, sent_cb);
7 }

可以看到这里调用了qemu_send_packet_async_with_flags函数,那么继续深入

 1 static ssize_t qemu_send_packet_async_with_flags(NetClientState *sender,
 2                                                  unsigned flags,
 3                                                  const uint8_t *buf, int size,
 4                                                  NetPacketSent *sent_cb)
 5 {
 6     NetQueue *queue;
 7
 8 #ifdef DEBUG_NET
 9     printf("qemu_send_packet_async:\n");
10     hex_dump(stdout, buf, size);
11 #endif
12 //如果发送端口的peer指针为空会发送失败,即不存在目标网卡
13     if (sender->link_down || !sender->peer) {
14         return size;
15     }
16 //获取对方的接收缓冲队列
17     queue = sender->peer->incoming_queue;
18
19     return qemu_net_queue_send(queue, sender, flags, buf, size, sent_cb);

这里就做了一下实质性的判断,查看下net client的连接是否打开并且对方net client是否存在,不满足条件直接返回,通过的话就获取对方的接收队列sender->peer->incoming_queue;注意这里是对方的接收队列,下面可以看到实际上只是把数据从buffer中复制到队列维护的链表中了。

然后调用qemu_net_queue_send函数进行发送

 1 ssize_t qemu_net_queue_send(NetQueue *queue,
 2                             NetClientState *sender,
 3                             unsigned flags,
 4                             const uint8_t *data,
 5                             size_t size,
 6                             NetPacketSent *sent_cb)
 7 {
 8     ssize_t ret;
 9 //这里表示如果queue正在发送就直接把buffer附加到队列的package链表,当然是在条件允许的情况下
10     if (queue->delivering || !qemu_can_send_packet(sender)) {
11         qemu_net_queue_append(queue, sender, flags, data, size, sent_cb);
12         return 0;
13     }
14 //否则需要启动队列进行数据的发送
15     ret = qemu_net_queue_deliver(queue, sender, flags, data, size);
16     if (ret == 0) {
17         qemu_net_queue_append(queue, sender, flags, data, size, sent_cb);
18         return 0;
19     }
20 //否则只能刷新下queue
21     qemu_net_queue_flush(queue);
22
23     return ret;
24 }

该函数就要做具体的工作了,首先判断队列是否正在进行发送,是的话直接调用qemu_net_queue_append函数把buffer附加到queue的发送链表中,否则还需要重新启动队列发送,然后在附加到发送链表。

假如都不成功就只能调用qemu_net_queue_flush函数重置下队列。

 1 static void qemu_net_queue_append(NetQueue *queue,
 2                                   NetClientState *sender,
 3                                   unsigned flags,
 4                                   const uint8_t *buf,
 5                                   size_t size,
 6                                   NetPacketSent *sent_cb)
 7 {
 8     NetPacket *packet;
 9
10     if (queue->nq_count >= queue->nq_maxlen && !sent_cb) {
11         return; /* drop if queue full and no callback */
12     }
13     packet = g_malloc(sizeof(NetPacket) + size);
14     packet->sender = sender;
15     packet->flags = flags;
16     packet->size = size;
17     packet->sent_cb = sent_cb;
18     memcpy(packet->data, buf, size);
19
20     queue->nq_count++;
21     QTAILQ_INSERT_TAIL(&queue->packets, packet, entry);
22 }

可以看到这里做的工作很简单,就是分配一个package把数据复制到里面,然后插入发送链表。



Hub 端

前面结合源代码大致分析了下Tap端数据的发送接收流程,本节介绍下Hub接收并转发数据包的流程,代码大部分都在hub.c中

相比前面的Tap。这里Hub完成的工作就要简单的多,代码量也要少很多,因为它其实并不分哪一端,只负责转发数据包,看下net_hub_receive函数

 1 static ssize_t net_hub_receive(NetHub *hub, NetHubPort *source_port,
 2                                const uint8_t *buf, size_t len)
 3 {
 4     NetHubPort *port;
 5 //收到数据包就从其他端口转发
 6     QLIST_FOREACH(port, &hub->ports, next) {
 7         if (port == source_port) {
 8             continue;
 9         }
10
11         qemu_send_packet(&port->nc, buf, len);
12     }
13     return len;
14 }

这里可以看到遍历Hub上的所有端口,然后调用qemu_send_packet函数对单个端口进行发送数据,其中忽略source_port。还有一个函数和这个函数相对就是net_hub_receive_iov,该函数以IO向量的方式对数据包做处理

 1 static ssize_t net_hub_receive_iov(NetHub *hub, NetHubPort *source_port,
 2                                    const struct iovec *iov, int iovcnt)
 3 {
 4     NetHubPort *port;
 5     ssize_t len = iov_size(iov, iovcnt);
 6
 7     QLIST_FOREACH(port, &hub->ports, next) {
 8         if (port == source_port) {
 9             continue;
10         }
11
12         qemu_sendv_packet(&port->nc, iov, iovcnt);
13     }
14     return len;
15 }

前面依然是遍历端口,不同的最后调用qemu_sendv_packet函数

1 ssize_t
2 qemu_sendv_packet(NetClientState *nc, const struct iovec *iov, int iovcnt)
3 {
4     return qemu_sendv_packet_async(nc, iov, iovcnt, NULL);
5 }
 1 ssize_t qemu_sendv_packet_async(NetClientState *sender,
 2                                 const struct iovec *iov, int iovcnt,
 3                                 NetPacketSent *sent_cb)
 4 {
 5     NetQueue *queue;
 6
 7     if (sender->link_down || !sender->peer) {
 8         return iov_size(iov, iovcnt);
 9     }
10
11     queue = sender->peer->incoming_queue;
12
13     return qemu_net_queue_send_iov(queue, sender,
14                                    QEMU_NET_PACKET_FLAG_NONE,
15                                    iov, iovcnt, sent_cb);
16 }

该函数是核心函数,这里的内容和前面Tap发送函数有些类似,sender是Hub上的转发端口的NetClientState结构,这里发送的 方式也是向对方的incoming_queue copy数据,唯一的区别在于这里采用的IO向量的方式,前面IO向量我们忽略了,这里就分析下,直接看向队列的链表中添加package的函数qemu_net_queue_append_iov

 1 static void qemu_net_queue_append_iov(NetQueue *queue,
 2                                       NetClientState *sender,
 3                                       unsigned flags,
 4                                       const struct iovec *iov,
 5                                       int iovcnt,
 6                                       NetPacketSent *sent_cb)
 7 {
 8     NetPacket *packet;
 9     size_t max_len = 0;
10     int i;
11
12     if (queue->nq_count >= queue->nq_maxlen && !sent_cb) {
13         return; /* drop if queue full and no callback */
14     }
15     for (i = 0; i < iovcnt; i++) {
16         max_len += iov[i].iov_len;
17     }
18
19     packet = g_malloc(sizeof(NetPacket) + max_len);
20     packet->sender = sender;
21     packet->sent_cb = sent_cb;
22     packet->flags = flags;
23     packet->size = 0;
24
25     for (i = 0; i < iovcnt; i++) {
26         size_t len = iov[i].iov_len;
27
28         memcpy(packet->data + packet->size, iov[i].iov_base, len);
29         packet->size += len;
30     }
31
32     queue->nq_count++;
33     QTAILQ_INSERT_TAIL(&queue->packets, packet, entry);
34 }

这里首先判断queue的发送链表是否已满,然后获取数据的长度,需要结合所有IO向量包含的数据长度和,最后申请一段内存做package,需要包含所有的数据以及NetPacket结构,并对package做一些参数的设置。然后逐项从向量代表的buffer中复制数据。最后在一次性的把整个package插入链表。



NIC端

终于到了关键的时刻,这里其实函数不多,但是函数体很庞大,我们看e1000网卡的接收数据流程,先看e1000_receive函数

 1 static ssize_t
 2 e1000_receive(NetClientState *nc, const uint8_t *buf, size_t size)
 3 {
 4     const struct iovec iov = {
 5         .iov_base = (uint8_t *)buf,
 6         .iov_len = size
 7     };
 8
 9     return e1000_receive_iov(nc, &iov, 1);
10 }

这里不管有没有使用IO向量都把数据封装到了一个向量里面,然后调用e1000_receive_iov函数,该函数的函数体比较庞大,按模块分析的话也并不难。

  2 static size_t e1000_receive_iov(NetClientState *nc, const struct iovec *iov, int iovcnt)
  3 {
  4     E1000State *s = qemu_get_nic_opaque(nc);
  5     PCIDevice *d = PCI_DEVICE(s);
  6     struct e1000_rx_desc desc;
  7     dma_addr_t base;
  8     unsigned int n, rdt;
  9     uint32_t rdh_start;
 10     uint16_t vlan_special = 0;
 11     uint8_t vlan_status = 0;
 12     uint8_t min_buf[MIN_BUF_SIZE];
 13     struct iovec min_iov;
 14     uint8_t *filter_buf = iov->iov_base;
 15     size_t size = iov_size(iov, iovcnt);
 16     size_t iov_ofs = 0;
 17     size_t desc_offset;
 18     size_t desc_size;
 19     size_t total_size;
 20
 21     if (!(s->mac_reg[STATUS] & E1000_STATUS_LU)) {
 22         return -1;
 23     }
 24
 25     if (!(s->mac_reg[RCTL] & E1000_RCTL_EN)) {
 26         return -1;
 27     }
 28
 29     /* Pad to minimum Ethernet frame length */
 30     if (size < sizeof(min_buf)) {
 31         iov_to_buf(iov, iovcnt, 0, min_buf, size);
 32         memset(&min_buf[size], 0, sizeof(min_buf) - size);
 33         min_iov.iov_base = filter_buf = min_buf;
 34         min_iov.iov_len = size = sizeof(min_buf);
 35         iovcnt = 1;
 36         iov = &min_iov;
 37     } else if (iov->iov_len < MAXIMUM_ETHERNET_HDR_LEN) {
 38         /* This is very unlikely, but may happen. */
 39         iov_to_buf(iov, iovcnt, 0, min_buf, MAXIMUM_ETHERNET_HDR_LEN);
 40         filter_buf = min_buf;
 41     }
 42
 43     /* Discard oversized packets if !LPE and !SBP. */
 44     if ((size > MAXIMUM_ETHERNET_LPE_SIZE ||
 45         (size > MAXIMUM_ETHERNET_VLAN_SIZE
 46         && !(s->mac_reg[RCTL] & E1000_RCTL_LPE)))
 47         && !(s->mac_reg[RCTL] & E1000_RCTL_SBP)) {
 48         return size;
 49     }
 50 //先对数据包进行过滤
 51     if (!receive_filter(s, filter_buf, size)) {
 52         return size;
 53     }
 54 //如果网卡支持vlan并且数据包是vlan数据包
 55     if (vlan_enabled(s) && is_vlan_packet(s, filter_buf)) {
 56         vlan_special = cpu_to_le16(be16_to_cpup((uint16_t *)(filter_buf
 57                                                                 + 14)));
 58         iov_ofs = 4;
 59         if (filter_buf == iov->iov_base) {
 60             memmove(filter_buf + 4, filter_buf, 12);//destination,src,count
 61             } else {
 62             iov_from_buf(iov, iovcnt, 4, filter_buf, 12);
 63             while (iov->iov_len <= iov_ofs) {
 64                 iov_ofs -= iov->iov_len;
 65                 iov++;
 66             }
 67         }
 68         vlan_status = E1000_RXD_STAT_VP;
 69         size -= 4;
 70     }
 71
 72     rdh_start = s->mac_reg[RDH];
 73     desc_offset = 0;
 74     total_size = size + fcs_len(s);//加上crc校验
 75     if (!e1000_has_rxbufs(s, total_size)) {
 76             set_ics(s, 0, E1000_ICS_RXO);
 77             return -1;
 78     }
 79     do {
 80         desc_size = total_size - desc_offset;
 81         if (desc_size > s->rxbuf_size) {
 82             desc_size = s->rxbuf_size;
 83         }
 84         base = rx_desc_base(s) + sizeof(desc) * s->mac_reg[RDH];
 85         pci_dma_read(d, base, &desc, sizeof(desc));
 86         desc.special = vlan_special;
 87         desc.status |= (vlan_status | E1000_RXD_STAT_DD);
 88         if (desc.buffer_addr) {
 89             if (desc_offset < size) {
 90                 size_t iov_copy;
 91                 hwaddr ba = le64_to_cpu(desc.buffer_addr);
 92                 size_t copy_size = size - desc_offset;
 93                 if (copy_size > s->rxbuf_size) {
 94                     copy_size = s->rxbuf_size;
 95                 }
 96                 do {
 97                     iov_copy = MIN(copy_size, iov->iov_len - iov_ofs);
 98                     pci_dma_write(d, ba, iov->iov_base + iov_ofs, iov_copy);
 99                     copy_size -= iov_copy;
100                     ba += iov_copy;
101                     iov_ofs += iov_copy;
102                     if (iov_ofs == iov->iov_len) {
103                         iov++;
104                         iov_ofs = 0;
105                     }
106                 } while (copy_size);
107             }
108             desc_offset += desc_size;
109             desc.length = cpu_to_le16(desc_size);
110             if (desc_offset >= total_size) {
111                 desc.status |= E1000_RXD_STAT_EOP | E1000_RXD_STAT_IXSM;
112             } else {
113                 /* Guest zeroing out status is not a hardware requirement.
114                    Clear EOP in case guest didn‘t do it. */
115                 desc.status &= ~E1000_RXD_STAT_EOP;
116             }
117         } else { // as per intel docs; skip descriptors with null buf addr
118             DBGOUT(RX, "Null RX descriptor!!\n");
119         }
120         pci_dma_write(d, base, &desc, sizeof(desc));
121
122         if (++s->mac_reg[RDH] * sizeof(desc) >= s->mac_reg[RDLEN])
123             s->mac_reg[RDH] = 0;
124         /* see comment in start_xmit; same here */
125         if (s->mac_reg[RDH] == rdh_start) {
126             DBGOUT(RXERR, "RDH wraparound @%x, RDT %x, RDLEN %x\n",
127                    rdh_start, s->mac_reg[RDT], s->mac_reg[RDLEN]);
128             set_ics(s, 0, E1000_ICS_RXO);
129             return -1;
130         }
131     } while (desc_offset < total_size);
132
133     s->mac_reg[GPRC]++;
134     s->mac_reg[TPR]++;
135     /* TOR - Total Octets Received:
136      * This register includes bytes received in a packet from the <Destination
137      * Address> field through the <CRC> field, inclusively.
138      */
139     n = s->mac_reg[TORL] + size + /* Always include FCS length. */ 4;
140     if (n < s->mac_reg[TORL])
141         s->mac_reg[TORH]++;
142     s->mac_reg[TORL] = n;
143
144     n = E1000_ICS_RXT0;
145     if ((rdt = s->mac_reg[RDT]) < s->mac_reg[RDH])
146         rdt += s->mac_reg[RDLEN] / sizeof(desc);
147     if (((rdt - s->mac_reg[RDH]) * sizeof(desc)) <= s->mac_reg[RDLEN] >>
148         s->rxbuf_min_shift)
149         n |= E1000_ICS_RXDMT0;
150
151     set_ics(s, 0, n);
152
153     return size;
154 }

结合上面的代码,首先进行的是判断数据的长度是否满足一个最小以太网帧的长度,如果不满足就必须按照以太网帧的最小长度对齐,即后面填充0即可。

然后丢弃超过最大标准的数据包;

接着就调用receive_filter函数对数据包进行过滤,这是数据链路层的过滤,需要判断数据包的类型(广播、组播或者网卡是混杂模式都直接接收),如果是单播需要分析链路层头部,比对MAC地址。

然后下面的do循环中就开始数据的写入,这是直接采用DMA的方式吧数据直接写入到客户机内存,然后向客户机注入软中断通知客户机。

写入的方式比较复杂,但是主要是逻辑混乱,也不难理解,这里就不重点描述。

最后写入完成调用set_ics注入软中断。剩下的就是客户机的操作了。

而E1000的发送函数就是start_xmit函数,位于E1000.c中。

static void
start_xmit(E1000State *s)
{
    PCIDevice *d = PCI_DEVICE(s);
    dma_addr_t base;
    struct e1000_tx_desc desc;
    uint32_t tdh_start = s->mac_reg[TDH], cause = E1000_ICS_TXQE;

    if (!(s->mac_reg[TCTL] & E1000_TCTL_EN)) {
        DBGOUT(TX, "tx disabled\n");
        return;
    }

    while (s->mac_reg[TDH] != s->mac_reg[TDT]) {
        base = tx_desc_base(s) +
               sizeof(struct e1000_tx_desc) * s->mac_reg[TDH];
        pci_dma_read(d, base, &desc, sizeof(desc));

        DBGOUT(TX, "index %d: %p : %x %x\n", s->mac_reg[TDH],
               (void *)(intptr_t)desc.buffer_addr, desc.lower.data,
               desc.upper.data);

        process_tx_desc(s, &desc);
        cause |= txdesc_writeback(s, base, &desc);

        if (++s->mac_reg[TDH] * sizeof(desc) >= s->mac_reg[TDLEN])
            s->mac_reg[TDH] = 0;
        /*
         * the following could happen only if guest sw assigns
         * bogus values to TDT/TDLEN.
         * there‘s nothing too intelligent we could do about this.
         */
        if (s->mac_reg[TDH] == tdh_start) {
            DBGOUT(TXERR, "TDH wraparound @%x, TDT %x, TDLEN %x\n",
                   tdh_start, s->mac_reg[TDT], s->mac_reg[TDLEN]);
            break;
        }
    }
    set_ics(s, 0, cause);
}

具体的步骤和接收数据的模式类似,网卡的发送寄存器会包含数据包的head和tail,如果两者不一致就说明有新数据包。然后获取发送缓冲区的地址,注意这里需要先获取对应本次传输数据的e1000_tx_desc结构,这也是首次调用pci_dma_read函数的作用,该结构中记录了数据buffer的实际地址,这个地址是需要再次通过DMA读取。获取到desc描述符后,就调用process_tx_desc(s, &desc)函数进行具体的传输数据DMA操作。

static void
process_tx_desc(E1000State *s, struct e1000_tx_desc *dp)
{
    PCIDevice *d = PCI_DEVICE(s);
    uint32_t txd_lower = le32_to_cpu(dp->lower.data);
    uint32_t dtype = txd_lower & (E1000_TXD_CMD_DEXT | E1000_TXD_DTYP_D);
    unsigned int split_size = txd_lower & 0xffff, bytes, sz, op;
    unsigned int msh = 0xfffff;
    uint64_t addr;
    struct e1000_context_desc *xp = (struct e1000_context_desc *)dp;
    struct e1000_tx *tp = &s->tx;

    s->mit_ide |= (txd_lower & E1000_TXD_CMD_IDE);
    if (dtype == E1000_TXD_CMD_DEXT) {    // context descriptor
        op = le32_to_cpu(xp->cmd_and_length);
        tp->ipcss = xp->lower_setup.ip_fields.ipcss;
        tp->ipcso = xp->lower_setup.ip_fields.ipcso;
        tp->ipcse = le16_to_cpu(xp->lower_setup.ip_fields.ipcse);
        tp->tucss = xp->upper_setup.tcp_fields.tucss;
        tp->tucso = xp->upper_setup.tcp_fields.tucso;
        tp->tucse = le16_to_cpu(xp->upper_setup.tcp_fields.tucse);
        tp->paylen = op & 0xfffff;
        tp->hdr_len = xp->tcp_seg_setup.fields.hdr_len;
        tp->mss = le16_to_cpu(xp->tcp_seg_setup.fields.mss);
        tp->ip = (op & E1000_TXD_CMD_IP) ? 1 : 0;
        tp->tcp = (op & E1000_TXD_CMD_TCP) ? 1 : 0;
        tp->tse = (op & E1000_TXD_CMD_TSE) ? 1 : 0;
        tp->tso_frames = 0;
        if (tp->tucso == 0) {    // this is probably wrong
            DBGOUT(TXSUM, "TCP/UDP: cso 0!\n");
            tp->tucso = tp->tucss + (tp->tcp ? 16 : 6);
        }
        return;
    } else if (dtype == (E1000_TXD_CMD_DEXT | E1000_TXD_DTYP_D)) {
        // data descriptor
        if (tp->size == 0) {
            tp->sum_needed = le32_to_cpu(dp->upper.data) >> 8;
        }
        tp->cptse = ( txd_lower & E1000_TXD_CMD_TSE ) ? 1 : 0;
    } else {
        // legacy descriptor
        tp->cptse = 0;
    }

    if (vlan_enabled(s) && is_vlan_txd(txd_lower) &&
        (tp->cptse || txd_lower & E1000_TXD_CMD_EOP)) {
        tp->vlan_needed = 1;
        stw_be_p(tp->vlan_header,
                      le16_to_cpup((uint16_t *)(s->mac_reg + VET)));
        stw_be_p(tp->vlan_header + 2,
                      le16_to_cpu(dp->upper.fields.special));
    }
    /*这里就是获取客户机中数据buffer的地址*/
    addr = le64_to_cpu(dp->buffer_addr);
    if (tp->tse && tp->cptse) {
        msh = tp->hdr_len + tp->mss;
        do {
            bytes = split_size;
            if (tp->size + bytes > msh)
                bytes = msh - tp->size;

            bytes = MIN(sizeof(tp->data) - tp->size, bytes);
            pci_dma_read(d, addr, tp->data + tp->size, bytes);
            sz = tp->size + bytes;
            if (sz >= tp->hdr_len && tp->size < tp->hdr_len) {
                memmove(tp->header, tp->data, tp->hdr_len);
            }
            tp->size = sz;
            addr += bytes;
            if (sz == msh) {
                xmit_seg(s);
                memmove(tp->data, tp->header, tp->hdr_len);
                tp->size = tp->hdr_len;
            }
        } while (split_size -= bytes);
    } else if (!tp->tse && tp->cptse) {
        // context descriptor TSE is not set, while data descriptor TSE is set
        DBGOUT(TXERR, "TCP segmentation error\n");
    } else {
        split_size = MIN(sizeof(tp->data) - tp->size, split_size);
        pci_dma_read(d, addr, tp->data + tp->size, split_size);
        tp->size += split_size;
    }

    if (!(txd_lower & E1000_TXD_CMD_EOP))
        return;
    if (!(tp->tse && tp->cptse && tp->size < tp->hdr_len)) {
        xmit_seg(s);
    }
    tp->tso_frames = 0;
    tp->sum_needed = 0;
    tp->vlan_needed = 0;
    tp->size = 0;
    tp->cptse = 0;

函数中需要首先判断描述符是什么类型。比较关键的是E1000_TXD_CMD_DEXTE1000_TXD_DTYP_D,

然后调用pci_dma_read函数把内存(客户机内存)中的数据读到设备缓冲区中,这点和实际的DMA道理是一样的。

然后调用xmit_seg

static void
xmit_seg(E1000State *s)
{
    uint16_t len, *sp;
    unsigned int frames = s->tx.tso_frames, css, sofar, n;
    struct e1000_tx *tp = &s->tx;

    if (tp->tse && tp->cptse) {
        css = tp->ipcss;
        DBGOUT(TXSUM, "frames %d size %d ipcss %d\n",
               frames, tp->size, css);
        if (tp->ip) {        // IPv4
            stw_be_p(tp->data+css+2, tp->size - css);
            stw_be_p(tp->data+css+4,
                          be16_to_cpup((uint16_t *)(tp->data+css+4))+frames);
        } else            // IPv6
            stw_be_p(tp->data+css+4, tp->size - css);
        css = tp->tucss;
        len = tp->size - css;
        DBGOUT(TXSUM, "tcp %d tucss %d len %d\n", tp->tcp, css, len);
        if (tp->tcp) {
            sofar = frames * tp->mss;
            stl_be_p(tp->data+css+4, ldl_be_p(tp->data+css+4)+sofar); /* seq */
            if (tp->paylen - sofar > tp->mss)
                tp->data[css + 13] &= ~9;        // PSH, FIN
        } else    // UDP
            stw_be_p(tp->data+css+4, len);
        if (tp->sum_needed & E1000_TXD_POPTS_TXSM) {
            unsigned int phsum;
            // add pseudo-header length before checksum calculation
            sp = (uint16_t *)(tp->data + tp->tucso);
            phsum = be16_to_cpup(sp) + len;
            phsum = (phsum >> 16) + (phsum & 0xffff);
            stw_be_p(sp, phsum);
        }
        tp->tso_frames++;
    }

    if (tp->sum_needed & E1000_TXD_POPTS_TXSM)
        putsum(tp->data, tp->size, tp->tucso, tp->tucss, tp->tucse);
    if (tp->sum_needed & E1000_TXD_POPTS_IXSM)
        putsum(tp->data, tp->size, tp->ipcso, tp->ipcss, tp->ipcse);
    if (tp->vlan_needed) {
        memmove(tp->vlan, tp->data, 4);
        memmove(tp->data, tp->data + 4, 8);
        memcpy(tp->data + 8, tp->vlan_header, 4);
        e1000_send_packet(s, tp->vlan, tp->size + 4);
    } else
        e1000_send_packet(s, tp->data, tp->size);
    s->mac_reg[TPT]++;
    s->mac_reg[GPTC]++;
    n = s->mac_reg[TOTL];
    if ((s->mac_reg[TOTL] += s->tx.size) < n)
        s->mac_reg[TOTH]++;
}

然后调用e1000_send_packet

static void
e1000_send_packet(E1000State *s, const uint8_t *buf, int size)
{
    NetClientState *nc = qemu_get_queue(s->nic);
    if (s->phy_reg[PHY_CTRL] & MII_CR_LOOPBACK) {
        nc->info->receive(nc, buf, size);
    } else {
        qemu_send_packet(nc, buf, size);
    }
}

最后就是qemu_send_packet,这就是之前我们分析过的函数了!!

时间: 2024-10-07 18:08:04

qemu网络虚拟化之数据流向分析三的相关文章

KDD Cup 99网络入侵检测数据的分析

看论文 该数据集是从一个模拟的美国空军局域网上采集来的 9 个星期的网络连接数据, 分成具有标识的训练数据和未加标识的测试数据.测试数据和训练数据有着不同的概率分布, 测试数据包含了一些未出现在训练数据中的攻击类型, 这使得入侵检测更具有现实性. 在训练集中包含了1种正常的标识类型 normal 和 22种训练攻击类型. 1.KDDCup99入侵检测实验数据的标识类型 标识类型 含义 具体分类标识 Normal 正常记录 normal DOS 拒绝服务攻击 back, land, neptune

深入浅出Hyper-V网络虚拟化(序)

服务器虚拟化已经越来越普及,很多企业也都在尝试着将现有业务转换成虚拟化的方式来运行,即在一个物理服务器上虚拟出多个实例,而每个实例彼此隔离,就好像在使用一台真实主机一样:网络虚拟化也同样如此,在同一条物理链路上虚拟出若干网络环境,且彼此隔离:不光如此,网络虚拟化还打破了传统VLAN以及IP地址分配方面的不便捷性,使用网络虚拟化可以把现有业务直接无缝的托管到IaaS中,而整个过程甚至无需考虑IP地址的重复性,现有应用的访问也不需要做任何修改 那么说到网络虚拟化就不得不提到“软件定义网络(SDN)”

网络数据包分析 网卡Offload

http://blog.nsfocus.net/network-packets-analysis-nic-offload/ 对于网络安全来说,网络传输数据包的捕获和分析是个基础工作,绿盟科技研究员在日常工作中,经常会捕获到一些大小远大于MTU值的数据包,经过分析这些大包的特性,发现和网卡的offload特性有关,本文对网卡Offload技术做简要描述. 文章目录 网络分片技术 网卡offload机制 发送模式 接收模式 网卡offload模式的设置 Linux windows 网卡Offload

[转]数据中心网络虚拟化 隧道技术

http://www.sdnlab.com/12077.html SDNLAB 如何实现不同租户和应用间的地址空间和数据流量的隔离是实现数据中心网络虚拟化首先需要解决的几个问题之一.所谓地址空间的隔离是指不同租户和应用之间的网络(ip)地址之间不会产生相互干扰.换句话说,两个租户完全可以使用相同的网络地址.所谓数据流量的隔离是指任何一个租户和应用都不会感知或捕获到其他虚拟网络内部的流量.为了实现上述目的,我们可以在物理网络上面为租户构建各自的覆盖(overlay)网络,而隧道封装技术则是实现覆盖

[转]数据中心网络虚拟化 主流平台产品介绍

http://www.sdnlab.com/12076.html SDNLAB 为了对数据中心网络虚拟化有个初步的认识,本文将对当前比较主流的几款商业平台进行介绍,包括VMware公司的网络虚拟化技术,IBM公司的Dove及开源的OpenDove平台, NEC公司的virtual-network-platform和VTN平台,以及Cisco公司的Nexus虚拟化平台. 1.Vmware公司的网络虚拟化技术 VMware在虚拟化领域的领导地位使得我们必须首先介绍一下他们的网络虚拟化技术NSX.然而

【微软公有云系列】Hyper-v(WinSer 2012 R2)网络虚拟化(三)租户隔离

前两章讲了挺多的理论知识,是希望让大家有一点了解,对网络基础不太好的朋友们或许看的会有点(◎﹏◎),不过不要紧,心里大概有个概念就行,接下来的会带给大家一些动手实践的Demo,加深大家对网络虚拟化(NVGRE)的了解. 租户隔离 我们知道,在公有云环境中,会有很多的企业来申请租户,那么他们之间为了确保信息的安全是需要把他们所租用的虚拟机之间要进行网络的相互隔离的,而且公有云目前不被一些企业所接受的,也是担心安全性的问题.当然安全性主要在于两部分,一部分是我们今天谈到的网络的隔离安全,还有一部分就

数据中心网络虚拟化-隧道技术

如何实现不同租户和应用间的地址空间和数据流量的隔离是实现数据中心网络虚拟化首先需要解决的几个问题之一.所谓地址空间的隔离是指不同租户和应用 之间的网络(ip)地址之间不会产生相互干扰.换句话说,两个租户完全可以使用相同的网络地址.所谓数据流量的隔离是指任何一个租户和应用都不会感知或捕 获到其他虚拟网络内部的流量.为了实现上述目的,我们可以在物理网络上面为租户构建各自的覆盖(overlay)网络,而隧道封装技术则是实现覆盖网络的 关键.本节我们将针对目前较为流行的构建覆盖网络的隧道封装技术展开讨论

数据中台专栏(三):数据质量分析及提升

本文作者:笑天 袋鼠云数据解决方案专家.拥有10余年IT行业开发和管理等企业服务经验,精通大型项目的开发和管理,曾就职于英特尔.索尼等世界500强企业,参与过英特尔,索尼,三星,华为等公司的大型项目的开发和咨询工作.近几年主攻大数据方向,包括数据中台建设.大数据治理.工业领域的数据应用等项目开发和实施. 正文:正文 大量的信息成倍增加,但有用的信息却非常有限. 信号是真相,噪声却使我们离真相越来越远. --<信号与噪声:大数据时代预测的科学与艺术> [美]纳特·西尔弗 一般情况下,企业都有多套

要将大数据和分析转变为竞争优势,实现业务转型,必须做到这三点!

所谓大数据(Big Data)是指不使用随机分析法(抽样调查)的捷径,而是采用对所有数据进行分析处理.它代表着需要新处理模式才能具有更强的决策力.洞察力和流程优化能力的海量.高增长率和多样化的信息资产.大数据的特点:4V Volume(大量).Velocity(高速).Variety(多样).Value(价值). 大数据技术的战略意义不在于掌握庞大的数据信息,而在于对这些含有意义的数据进行专业化处理,通过提高对数据的"加工能力",来实现数据的"增值". 1.黑暗数据