Commit b6503ed9b8815ecfb82fe9faba28936365321248
1 parent
3831ab20
qemu:virtio-net: Add additional MACs via a filter table (Alex Williamson)
Create a filter table and allow the guest to populate it with the MAC class control commands. We manage the size and usage of the filter table including enabling promiscuous and all-multi modes as necessary. The guest should therefore assume the table is infinite. Eventually this might allow us to bind directly to a hardware NIC and manipulate a physical MAC filter. The specifics of the TABLE_SET command are documented in virtio-net.h. Separate buffers in the same command are used for unicaste and multicast addresses for priority and sychronization. With this we can export the VIRTIO_NET_F_CTRL_RX feature bit. Signed-off-by: Alex Williamson <alex.williamson@hp.com> Signed-off-by: Anthony Liguori <aliguori@us.ibm.com> git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@6539 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
2 changed files
with
109 additions
and
2 deletions
hw/virtio-net.c
@@ -16,7 +16,9 @@ | @@ -16,7 +16,9 @@ | ||
16 | #include "qemu-timer.h" | 16 | #include "qemu-timer.h" |
17 | #include "virtio-net.h" | 17 | #include "virtio-net.h" |
18 | 18 | ||
19 | -#define VIRTIO_NET_VM_VERSION 4 | 19 | +#define VIRTIO_NET_VM_VERSION 5 |
20 | + | ||
21 | +#define MAC_TABLE_ENTRIES 32 | ||
20 | 22 | ||
21 | typedef struct VirtIONet | 23 | typedef struct VirtIONet |
22 | { | 24 | { |
@@ -32,6 +34,10 @@ typedef struct VirtIONet | @@ -32,6 +34,10 @@ typedef struct VirtIONet | ||
32 | int mergeable_rx_bufs; | 34 | int mergeable_rx_bufs; |
33 | int promisc; | 35 | int promisc; |
34 | int allmulti; | 36 | int allmulti; |
37 | + struct { | ||
38 | + int in_use; | ||
39 | + uint8_t *macs; | ||
40 | + } mac_table; | ||
35 | } VirtIONet; | 41 | } VirtIONet; |
36 | 42 | ||
37 | /* TODO | 43 | /* TODO |
@@ -87,13 +93,18 @@ static void virtio_net_reset(VirtIODevice *vdev) | @@ -87,13 +93,18 @@ static void virtio_net_reset(VirtIODevice *vdev) | ||
87 | /* Reset back to compatibility mode */ | 93 | /* Reset back to compatibility mode */ |
88 | n->promisc = 1; | 94 | n->promisc = 1; |
89 | n->allmulti = 0; | 95 | n->allmulti = 0; |
96 | + | ||
97 | + /* Flush any MAC filter table state */ | ||
98 | + n->mac_table.in_use = 0; | ||
99 | + memset(n->mac_table.macs, 0, MAC_TABLE_ENTRIES * ETH_ALEN); | ||
90 | } | 100 | } |
91 | 101 | ||
92 | static uint32_t virtio_net_get_features(VirtIODevice *vdev) | 102 | static uint32_t virtio_net_get_features(VirtIODevice *vdev) |
93 | { | 103 | { |
94 | uint32_t features = (1 << VIRTIO_NET_F_MAC) | | 104 | uint32_t features = (1 << VIRTIO_NET_F_MAC) | |
95 | (1 << VIRTIO_NET_F_STATUS) | | 105 | (1 << VIRTIO_NET_F_STATUS) | |
96 | - (1 << VIRTIO_NET_F_CTRL_VQ); | 106 | + (1 << VIRTIO_NET_F_CTRL_VQ) | |
107 | + (1 << VIRTIO_NET_F_CTRL_RX); | ||
97 | 108 | ||
98 | return features; | 109 | return features; |
99 | } | 110 | } |
@@ -127,6 +138,53 @@ static int virtio_net_handle_rx_mode(VirtIONet *n, uint8_t cmd, | @@ -127,6 +138,53 @@ static int virtio_net_handle_rx_mode(VirtIONet *n, uint8_t cmd, | ||
127 | return VIRTIO_NET_OK; | 138 | return VIRTIO_NET_OK; |
128 | } | 139 | } |
129 | 140 | ||
141 | +static int virtio_net_handle_mac(VirtIONet *n, uint8_t cmd, | ||
142 | + VirtQueueElement *elem) | ||
143 | +{ | ||
144 | + struct virtio_net_ctrl_mac mac_data; | ||
145 | + | ||
146 | + if (cmd != VIRTIO_NET_CTRL_MAC_TABLE_SET || elem->out_num != 3 || | ||
147 | + elem->out_sg[1].iov_len < sizeof(mac_data) || | ||
148 | + elem->out_sg[2].iov_len < sizeof(mac_data)) | ||
149 | + return VIRTIO_NET_ERR; | ||
150 | + | ||
151 | + n->mac_table.in_use = 0; | ||
152 | + memset(n->mac_table.macs, 0, MAC_TABLE_ENTRIES * ETH_ALEN); | ||
153 | + | ||
154 | + mac_data.entries = ldl_le_p(elem->out_sg[1].iov_base); | ||
155 | + | ||
156 | + if (sizeof(mac_data.entries) + | ||
157 | + (mac_data.entries * ETH_ALEN) > elem->out_sg[1].iov_len) | ||
158 | + return VIRTIO_NET_ERR; | ||
159 | + | ||
160 | + if (mac_data.entries <= MAC_TABLE_ENTRIES) { | ||
161 | + memcpy(n->mac_table.macs, elem->out_sg[1].iov_base + sizeof(mac_data), | ||
162 | + mac_data.entries * ETH_ALEN); | ||
163 | + n->mac_table.in_use += mac_data.entries; | ||
164 | + } else { | ||
165 | + n->promisc = 1; | ||
166 | + return VIRTIO_NET_OK; | ||
167 | + } | ||
168 | + | ||
169 | + mac_data.entries = ldl_le_p(elem->out_sg[2].iov_base); | ||
170 | + | ||
171 | + if (sizeof(mac_data.entries) + | ||
172 | + (mac_data.entries * ETH_ALEN) > elem->out_sg[2].iov_len) | ||
173 | + return VIRTIO_NET_ERR; | ||
174 | + | ||
175 | + if (mac_data.entries) { | ||
176 | + if (n->mac_table.in_use + mac_data.entries <= MAC_TABLE_ENTRIES) { | ||
177 | + memcpy(n->mac_table.macs + (n->mac_table.in_use * ETH_ALEN), | ||
178 | + elem->out_sg[2].iov_base + sizeof(mac_data), | ||
179 | + mac_data.entries * ETH_ALEN); | ||
180 | + n->mac_table.in_use += mac_data.entries; | ||
181 | + } else | ||
182 | + n->allmulti = 1; | ||
183 | + } | ||
184 | + | ||
185 | + return VIRTIO_NET_OK; | ||
186 | +} | ||
187 | + | ||
130 | static void virtio_net_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) | 188 | static void virtio_net_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) |
131 | { | 189 | { |
132 | VirtIONet *n = to_virtio_net(vdev); | 190 | VirtIONet *n = to_virtio_net(vdev); |
@@ -151,6 +209,8 @@ static void virtio_net_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) | @@ -151,6 +209,8 @@ static void virtio_net_handle_ctrl(VirtIODevice *vdev, VirtQueue *vq) | ||
151 | 209 | ||
152 | if (ctrl.class == VIRTIO_NET_CTRL_RX_MODE) | 210 | if (ctrl.class == VIRTIO_NET_CTRL_RX_MODE) |
153 | status = virtio_net_handle_rx_mode(n, ctrl.cmd, &elem); | 211 | status = virtio_net_handle_rx_mode(n, ctrl.cmd, &elem); |
212 | + else if (ctrl.class == VIRTIO_NET_CTRL_MAC) | ||
213 | + status = virtio_net_handle_mac(n, ctrl.cmd, &elem); | ||
154 | 214 | ||
155 | stb_p(elem.in_sg[elem.in_num - 1].iov_base, status); | 215 | stb_p(elem.in_sg[elem.in_num - 1].iov_base, status); |
156 | 216 | ||
@@ -226,6 +286,7 @@ static int receive_filter(VirtIONet *n, const uint8_t *buf, int size) | @@ -226,6 +286,7 @@ static int receive_filter(VirtIONet *n, const uint8_t *buf, int size) | ||
226 | { | 286 | { |
227 | static const uint8_t bcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; | 287 | static const uint8_t bcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; |
228 | uint8_t *ptr = (uint8_t *)buf; | 288 | uint8_t *ptr = (uint8_t *)buf; |
289 | + int i; | ||
229 | 290 | ||
230 | if (n->promisc) | 291 | if (n->promisc) |
231 | return 1; | 292 | return 1; |
@@ -244,6 +305,11 @@ static int receive_filter(VirtIONet *n, const uint8_t *buf, int size) | @@ -244,6 +305,11 @@ static int receive_filter(VirtIONet *n, const uint8_t *buf, int size) | ||
244 | if (!memcmp(ptr, n->mac, ETH_ALEN)) | 305 | if (!memcmp(ptr, n->mac, ETH_ALEN)) |
245 | return 1; | 306 | return 1; |
246 | 307 | ||
308 | + for (i = 0; i < n->mac_table.in_use; i++) { | ||
309 | + if (!memcmp(ptr, &n->mac_table.macs[i * ETH_ALEN], ETH_ALEN)) | ||
310 | + return 1; | ||
311 | + } | ||
312 | + | ||
247 | return 0; | 313 | return 0; |
248 | } | 314 | } |
249 | 315 | ||
@@ -406,6 +472,8 @@ static void virtio_net_save(QEMUFile *f, void *opaque) | @@ -406,6 +472,8 @@ static void virtio_net_save(QEMUFile *f, void *opaque) | ||
406 | qemu_put_be16(f, n->status); | 472 | qemu_put_be16(f, n->status); |
407 | qemu_put_be32(f, n->promisc); | 473 | qemu_put_be32(f, n->promisc); |
408 | qemu_put_be32(f, n->allmulti); | 474 | qemu_put_be32(f, n->allmulti); |
475 | + qemu_put_be32(f, n->mac_table.in_use); | ||
476 | + qemu_put_buffer(f, n->mac_table.macs, n->mac_table.in_use * ETH_ALEN); | ||
409 | } | 477 | } |
410 | 478 | ||
411 | static int virtio_net_load(QEMUFile *f, void *opaque, int version_id) | 479 | static int virtio_net_load(QEMUFile *f, void *opaque, int version_id) |
@@ -429,6 +497,19 @@ static int virtio_net_load(QEMUFile *f, void *opaque, int version_id) | @@ -429,6 +497,19 @@ static int virtio_net_load(QEMUFile *f, void *opaque, int version_id) | ||
429 | n->allmulti = qemu_get_be32(f); | 497 | n->allmulti = qemu_get_be32(f); |
430 | } | 498 | } |
431 | 499 | ||
500 | + if (version_id >= 5) { | ||
501 | + n->mac_table.in_use = qemu_get_be32(f); | ||
502 | + /* MAC_TABLE_ENTRIES may be different from the saved image */ | ||
503 | + if (n->mac_table.in_use <= MAC_TABLE_ENTRIES) { | ||
504 | + qemu_get_buffer(f, n->mac_table.macs, | ||
505 | + n->mac_table.in_use * ETH_ALEN); | ||
506 | + } else if (n->mac_table.in_use) { | ||
507 | + qemu_fseek(f, n->mac_table.in_use * ETH_ALEN, SEEK_CUR); | ||
508 | + n->promisc = 1; | ||
509 | + n->mac_table.in_use = 0; | ||
510 | + } | ||
511 | + } | ||
512 | + | ||
432 | if (n->tx_timer_active) { | 513 | if (n->tx_timer_active) { |
433 | qemu_mod_timer(n->tx_timer, | 514 | qemu_mod_timer(n->tx_timer, |
434 | qemu_get_clock(vm_clock) + TX_TIMER_INTERVAL); | 515 | qemu_get_clock(vm_clock) + TX_TIMER_INTERVAL); |
@@ -474,6 +555,10 @@ void virtio_net_init(PCIBus *bus, NICInfo *nd, int devfn) | @@ -474,6 +555,10 @@ void virtio_net_init(PCIBus *bus, NICInfo *nd, int devfn) | ||
474 | n->mergeable_rx_bufs = 0; | 555 | n->mergeable_rx_bufs = 0; |
475 | n->promisc = 1; /* for compatibility */ | 556 | n->promisc = 1; /* for compatibility */ |
476 | 557 | ||
558 | + n->mac_table.macs = qemu_mallocz(MAC_TABLE_ENTRIES * ETH_ALEN); | ||
559 | + if (!n->mac_table.macs) | ||
560 | + return; | ||
561 | + | ||
477 | register_savevm("virtio-net", virtio_net_id++, VIRTIO_NET_VM_VERSION, | 562 | register_savevm("virtio-net", virtio_net_id++, VIRTIO_NET_VM_VERSION, |
478 | virtio_net_save, virtio_net_load, n); | 563 | virtio_net_save, virtio_net_load, n); |
479 | } | 564 | } |
hw/virtio-net.h
@@ -113,4 +113,26 @@ typedef uint8_t virtio_net_ctrl_ack; | @@ -113,4 +113,26 @@ typedef uint8_t virtio_net_ctrl_ack; | ||
113 | #define VIRTIO_NET_CTRL_RX_MODE_PROMISC 0 | 113 | #define VIRTIO_NET_CTRL_RX_MODE_PROMISC 0 |
114 | #define VIRTIO_NET_CTRL_RX_MODE_ALLMULTI 1 | 114 | #define VIRTIO_NET_CTRL_RX_MODE_ALLMULTI 1 |
115 | 115 | ||
116 | +/* | ||
117 | + * Control the MAC filter table. | ||
118 | + * | ||
119 | + * The MAC filter table is managed by the hypervisor, the guest should | ||
120 | + * assume the size is infinite. Filtering should be considered | ||
121 | + * non-perfect, ie. based on hypervisor resources, the guest may | ||
122 | + * received packets from sources not specified in the filter list. | ||
123 | + * | ||
124 | + * In addition to the class/cmd header, the TABLE_SET command requires | ||
125 | + * two out scatterlists. Each contains a 4 byte count of entries followed | ||
126 | + * by a concatenated byte stream of the ETH_ALEN MAC addresses. The | ||
127 | + * first sg list contains unicast addresses, the second is for multicast. | ||
128 | + * This functionality is present if the VIRTIO_NET_F_CTRL_RX feature | ||
129 | + * is available. | ||
130 | + */ | ||
131 | +struct virtio_net_ctrl_mac { | ||
132 | + uint32_t entries; | ||
133 | + uint8_t macs[][ETH_ALEN]; | ||
134 | +}; | ||
135 | +#define VIRTIO_NET_CTRL_MAC 1 | ||
136 | + #define VIRTIO_NET_CTRL_MAC_TABLE_SET 0 | ||
137 | + | ||
116 | #endif | 138 | #endif |