#include "sysemu.h"
#include "qemu-common.h"
+#include "qemu-error.h"
+
+#define PATH_NET_TUN "/dev/net/tun"
int tap_open(char *ifname, int ifname_size, int *vnet_hdr, int vnet_hdr_required)
{
struct ifreq ifr;
int fd, ret;
- TFR(fd = open("/dev/net/tun", O_RDWR));
+ TFR(fd = open(PATH_NET_TUN, O_RDWR));
if (fd < 0) {
- fprintf(stderr, "warning: could not open /dev/net/tun: no virtual network emulation\n");
+ error_report("could not open %s: %m", PATH_NET_TUN);
return -1;
}
memset(&ifr, 0, sizeof(ifr));
features & IFF_VNET_HDR) {
*vnet_hdr = 1;
ifr.ifr_flags |= IFF_VNET_HDR;
+ } else {
+ *vnet_hdr = 0;
}
if (vnet_hdr_required && !*vnet_hdr) {
- qemu_error("vnet_hdr=1 requested, but no kernel "
- "support for IFF_VNET_HDR available");
+ error_report("vnet_hdr=1 requested, but no kernel "
+ "support for IFF_VNET_HDR available");
close(fd);
return -1;
}
pstrcpy(ifr.ifr_name, IFNAMSIZ, "tap%d");
ret = ioctl(fd, TUNSETIFF, (void *) &ifr);
if (ret != 0) {
- fprintf(stderr, "warning: could not configure /dev/net/tun: no virtual network emulation\n");
+ if (ifname[0] != '\0') {
+ error_report("could not configure %s (%s): %m", PATH_NET_TUN, ifr.ifr_name);
+ } else {
+ error_report("could not configure %s: %m", PATH_NET_TUN);
+ }
close(fd);
return -1;
}
return fd;
}
-/* sndbuf should be set to a value lower than the tx queue
- * capacity of any destination network interface.
+/* sndbuf implements a kind of flow control for tap.
+ * Unfortunately when it's enabled, and packets are sent
+ * to other guests on the same host, the receiver
+ * can lock up the transmitter indefinitely.
+ *
+ * To avoid packet loss, sndbuf should be set to a value lower than the tx
+ * queue capacity of any destination network interface.
* Ethernet NICs generally have txqueuelen=1000, so 1Mb is
- * a good default, given a 1500 byte MTU.
+ * a good value, given a 1500 byte MTU.
*/
-#define TAP_DEFAULT_SNDBUF 1024*1024
+#define TAP_DEFAULT_SNDBUF 0
-int tap_set_sndbuf(int fd, QemuOpts *opts)
+int tap_set_sndbuf(int fd, const NetdevTapOptions *tap)
{
int sndbuf;
- sndbuf = qemu_opt_get_size(opts, "sndbuf", TAP_DEFAULT_SNDBUF);
+ sndbuf = !tap->has_sndbuf ? TAP_DEFAULT_SNDBUF :
+ tap->sndbuf > INT_MAX ? INT_MAX :
+ tap->sndbuf;
+
if (!sndbuf) {
sndbuf = INT_MAX;
}
- if (ioctl(fd, TUNSETSNDBUF, &sndbuf) == -1 && qemu_opt_get(opts, "sndbuf")) {
- qemu_error("TUNSETSNDBUF ioctl failed: %s\n", strerror(errno));
+ if (ioctl(fd, TUNSETSNDBUF, &sndbuf) == -1 && tap->has_sndbuf) {
+ error_report("TUNSETSNDBUF ioctl failed: %s", strerror(errno));
return -1;
}
return 0;
struct ifreq ifr;
if (ioctl(fd, TUNGETIFF, &ifr) != 0) {
- qemu_error("TUNGETIFF ioctl() failed: %s\n", strerror(errno));
+ error_report("TUNGETIFF ioctl() failed: %s", strerror(errno));
return 0;
}
return ifr.ifr_flags & IFF_VNET_HDR;
}
+int tap_probe_has_ufo(int fd)
+{
+ unsigned offload;
+
+ offload = TUN_F_CSUM | TUN_F_UFO;
+
+ if (ioctl(fd, TUNSETOFFLOAD, offload) < 0)
+ return 0;
+
+ return 1;
+}
+
+/* Verify that we can assign given length */
+int tap_probe_vnet_hdr_len(int fd, int len)
+{
+ int orig;
+ if (ioctl(fd, TUNGETVNETHDRSZ, &orig) == -1) {
+ return 0;
+ }
+ if (ioctl(fd, TUNSETVNETHDRSZ, &len) == -1) {
+ return 0;
+ }
+ /* Restore original length: we can't handle failure. */
+ if (ioctl(fd, TUNSETVNETHDRSZ, &orig) == -1) {
+ fprintf(stderr, "TUNGETVNETHDRSZ ioctl() failed: %s. Exiting.\n",
+ strerror(errno));
+ assert(0);
+ return -errno;
+ }
+ return 1;
+}
+
+void tap_fd_set_vnet_hdr_len(int fd, int len)
+{
+ if (ioctl(fd, TUNSETVNETHDRSZ, &len) == -1) {
+ fprintf(stderr, "TUNSETVNETHDRSZ ioctl() failed: %s. Exiting.\n",
+ strerror(errno));
+ assert(0);
+ }
+}
+
void tap_fd_set_offload(int fd, int csum, int tso4,
int tso6, int ecn, int ufo)
{
unsigned int offload = 0;
+ /* Check if our kernel supports TUNSETOFFLOAD */
+ if (ioctl(fd, TUNSETOFFLOAD, 0) != 0 && errno == EINVAL) {
+ return;
+ }
+
if (csum) {
offload |= TUN_F_CSUM;
if (tso4)