diff --git a/.gitignore b/.gitignore index 460e10a..8ea4a22 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -SOURCES/usbredir-0.12.0.tar.xz +SOURCES/usbredir-0.13.0.tar.xz diff --git a/.usbredir.metadata b/.usbredir.metadata index 2bfab34..7c70852 100644 --- a/.usbredir.metadata +++ b/.usbredir.metadata @@ -1 +1 @@ -70940f6dc409b3bdb9ee98f24690c438f1ae999e SOURCES/usbredir-0.12.0.tar.xz +7a64e4d8a52527edf41bfcd60449b89db0c0f5b2 SOURCES/usbredir-0.13.0.tar.xz diff --git a/SOURCES/0001-Revert-remove-usbredirserver.patch b/SOURCES/0001-Revert-remove-usbredirserver.patch new file mode 100644 index 0000000..adb031f --- /dev/null +++ b/SOURCES/0001-Revert-remove-usbredirserver.patch @@ -0,0 +1,593 @@ +From e06a042c1c0eccefeccc63e419b9b09bef11e28f Mon Sep 17 00:00:00 2001 +From: Victor Toso +Date: Wed, 30 Nov 2022 21:02:20 +0100 +Subject: [PATCH] Revert "remove usbredirserver" +Content-type: text/plain + +This reverts commit f4ffdce329305da2803684776f7659083a530819. +--- + README.md | 4 + + meson.build | 1 + + usbredirserver/meson.build | 12 + + usbredirserver/usbredirserver.1 | 41 +++ + usbredirserver/usbredirserver.c | 474 ++++++++++++++++++++++++++++++++ + 5 files changed, 532 insertions(+) + create mode 100644 usbredirserver/meson.build + create mode 100644 usbredirserver/usbredirserver.1 + create mode 100644 usbredirserver/usbredirserver.c + +diff --git a/README.md b/README.md +index ed04cca..2acb7b0 100644 +--- a/README.md ++++ b/README.md +@@ -28,6 +28,10 @@ The usbredirect binary is an usbredir client for exporting an USB device either + as TCP client or server, for use from another (virtual) machine through the + usbredir protocol. + ++## usbredirserver ++ ++A simple tcp server usb-host, using usbredirhost ++ + ## usbredirtestclient + + A small testclient for the usbredir protocol over tcp, using usbredirparser +diff --git a/meson.build b/meson.build +index aac9909..e740778 100644 +--- a/meson.build ++++ b/meson.build +@@ -103,6 +103,7 @@ if get_option('tools').enabled() + subdir('tools') + endif + if host_machine.system() != 'windows' ++ subdir('usbredirserver') + subdir('usbredirtestclient') + + if get_option('fuzzing').enabled() +diff --git a/usbredirserver/meson.build b/usbredirserver/meson.build +new file mode 100644 +index 0000000..de40681 +--- /dev/null ++++ b/usbredirserver/meson.build +@@ -0,0 +1,12 @@ ++usbredirserver_sources = [ ++ 'usbredirserver.c', ++] ++ ++executable('usbredirserver', ++ sources : usbredirserver_sources, ++ c_args : '-Wno-deprecated-declarations', ++ install : true, ++ install_dir: get_option('sbindir'), ++ dependencies : usbredir_host_lib_dep) ++ ++install_man('usbredirserver.1') +diff --git a/usbredirserver/usbredirserver.1 b/usbredirserver/usbredirserver.1 +new file mode 100644 +index 0000000..d5a6793 +--- /dev/null ++++ b/usbredirserver/usbredirserver.1 +@@ -0,0 +1,41 @@ ++.TH USBREDIRSERVER "1" "April 2012" "usbredirserver" "User Commands" ++.SH NAME ++usbredirserver \- exporting an USB device for use from another (virtual) machine ++.SH SYNOPSIS ++.B usbredirserver ++[\fI-p|--port \fR] [\fI-v|--verbose <0-5>\fR] [\fI-4 ] ++\fI\fR ++.SH DESCRIPTION ++usbredirserver is a small standalone server for exporting an USB device for ++use from another (virtual) machine through the usbredir protocol. ++.PP ++You can specify the USB device to export either by USB id in the form of ++\fI:\fR, or by USB bus number and device address in the form ++of \fI-\fR. ++.PP ++Notice that an instance of usbredirserver can only be used to export a ++single USB device. If you want to export multiple devices you can start ++multiple instances listening on different TCP ports. ++.SH OPTIONS ++.TP ++\fB\-p\fR, \fB\-\-port\fR=\fIPORT\fR ++Set the TCP port to listen on to \fIPORT\fR ++.TP ++\fB\-v\fR, \fB\-\-verbose\fR=\fIVERBOSE\fR ++Set usbredirserver's verbosity level to \fIVERBOSE\fR, this mostly affects USB ++redirection related messages. Valid values are 0-5: ++.br ++0:Silent 1:Errors 2:Warnings 3:Info 4:Debug 5:Debug++ ++.SH AUTHOR ++Written by Hans de Goede ++.SH REPORTING BUGS ++You can report bugs to the spice-devel mailinglist: ++http://lists.freedesktop.org/mailman/listinfo/spice-devel ++or filing an issue at: ++https://gitlab.freedesktop.org/spice/usbredir/issues/new ++.SH COPYRIGHT ++Copyright 2010-2012 Red Hat, Inc. ++License GPLv2+: GNU GPL version 2 or later . ++.br ++This is free software: you are free to change and redistribute it. ++There is NO WARRANTY, to the extent permitted by law. +diff --git a/usbredirserver/usbredirserver.c b/usbredirserver/usbredirserver.c +new file mode 100644 +index 0000000..badb7bc +--- /dev/null ++++ b/usbredirserver/usbredirserver.c +@@ -0,0 +1,474 @@ ++/* usbredirserver.c simple usb network redirection tcp/ip server (host). ++ ++ Copyright 2010-2011 Red Hat, Inc. ++ ++ Red Hat Authors: ++ Hans de Goede ++ ++ This program is free software; you can redistribute it and/or ++ modify it under the terms of the GNU General Public ++ License as published by the Free Software Foundation; either ++ version 2 of the License, or (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License ++ along with this library; if not, see . ++*/ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "usbredirhost.h" ++ ++ ++#define SERVER_VERSION "usbredirserver " PACKAGE_VERSION ++ ++#if !defined(SOL_TCP) && defined(IPPROTO_TCP) ++#define SOL_TCP IPPROTO_TCP ++#endif ++#if !defined(TCP_KEEPIDLE) && defined(TCP_KEEPALIVE) && defined(__APPLE__) ++#define TCP_KEEPIDLE TCP_KEEPALIVE ++#endif ++ ++static int verbose = usbredirparser_info; ++static int client_fd, running = 1; ++static libusb_context *ctx; ++static struct usbredirhost *host; ++ ++static const struct option longopts[] = { ++ { "port", required_argument, NULL, 'p' }, ++ { "verbose", required_argument, NULL, 'v' }, ++ { "ipv4", required_argument, NULL, '4' }, ++ { "ipv6", required_argument, NULL, '6' }, ++ { "keepalive", required_argument, NULL, 'k' }, ++ { "help", no_argument, NULL, 'h' }, ++ { NULL, 0, NULL, 0 } ++}; ++ ++static void usbredirserver_log(void *priv, int level, const char *msg) ++{ ++ if (level <= verbose) ++ fprintf(stderr, "%s\n", msg); ++} ++ ++static int usbredirserver_read(void *priv, uint8_t *data, int count) ++{ ++ int r = read(client_fd, data, count); ++ if (r < 0) { ++ if (errno == EAGAIN) ++ return 0; ++ return -1; ++ } ++ if (r == 0) { /* Client disconnected */ ++ close(client_fd); ++ client_fd = -1; ++ } ++ return r; ++} ++ ++static int usbredirserver_write(void *priv, uint8_t *data, int count) ++{ ++ int r = write(client_fd, data, count); ++ if (r < 0) { ++ if (errno == EAGAIN) ++ return 0; ++ if (errno == EPIPE) { /* Client disconnected */ ++ close(client_fd); ++ client_fd = -1; ++ return 0; ++ } ++ return -1; ++ } ++ return r; ++} ++ ++static void usage(int exit_code, char *argv0) ++{ ++ fprintf(exit_code? stderr:stdout, ++ "Usage: %s [-p|--port ] [-v|--verbose <0-5>] " ++ "[[-4|--ipv4 ipaddr]|[-6|--ipv6 ipaddr]] " ++ "[-k|--keepalive seconds] " ++ "\n", ++ argv0); ++ exit(exit_code); ++} ++ ++static void invalid_usb_device_id(char *usb_device_id, char *argv0) ++{ ++ fprintf(stderr, "Invalid usb device identifier: %s\n", usb_device_id); ++ usage(1, argv0); ++} ++ ++static void run_main_loop(void) ++{ ++ const struct libusb_pollfd **pollfds = NULL; ++ fd_set readfds, writefds; ++ int i, n, nfds; ++ struct timeval timeout, *timeout_p; ++ ++ while (running && client_fd != -1) { ++ FD_ZERO(&readfds); ++ FD_ZERO(&writefds); ++ ++ FD_SET(client_fd, &readfds); ++ if (usbredirhost_has_data_to_write(host)) { ++ FD_SET(client_fd, &writefds); ++ } ++ nfds = client_fd + 1; ++ ++ free(pollfds); ++ pollfds = libusb_get_pollfds(ctx); ++ for (i = 0; pollfds && pollfds[i]; i++) { ++ if (pollfds[i]->events & POLLIN) { ++ FD_SET(pollfds[i]->fd, &readfds); ++ } ++ if (pollfds[i]->events & POLLOUT) { ++ FD_SET(pollfds[i]->fd, &writefds); ++ } ++ if (pollfds[i]->fd >= nfds) ++ nfds = pollfds[i]->fd + 1; ++ } ++ ++ if (libusb_get_next_timeout(ctx, &timeout) == 1) { ++ timeout_p = &timeout; ++ } else { ++ timeout_p = NULL; ++ } ++ n = select(nfds, &readfds, &writefds, NULL, timeout_p); ++ if (n == -1) { ++ if (errno == EINTR) { ++ continue; ++ } ++ perror("select"); ++ break; ++ } ++ memset(&timeout, 0, sizeof(timeout)); ++ if (n == 0) { ++ libusb_handle_events_timeout(ctx, &timeout); ++ continue; ++ } ++ ++ if (FD_ISSET(client_fd, &readfds)) { ++ if (usbredirhost_read_guest_data(host)) { ++ break; ++ } ++ } ++ /* usbredirhost_read_guest_data may have detected client disconnect */ ++ if (client_fd == -1) ++ break; ++ ++ if (FD_ISSET(client_fd, &writefds)) { ++ if (usbredirhost_write_guest_data(host)) { ++ break; ++ } ++ } ++ ++ for (i = 0; pollfds && pollfds[i]; i++) { ++ if (FD_ISSET(pollfds[i]->fd, &readfds) || ++ FD_ISSET(pollfds[i]->fd, &writefds)) { ++ libusb_handle_events_timeout(ctx, &timeout); ++ break; ++ } ++ } ++ } ++ if (client_fd != -1) { /* Broken out of the loop because of an error ? */ ++ close(client_fd); ++ client_fd = -1; ++ } ++ free(pollfds); ++} ++ ++static void quit_handler(int sig) ++{ ++ running = 0; ++} ++ ++int main(int argc, char *argv[]) ++{ ++ int o, flags, server_fd = -1; ++ char *endptr, *delim; ++ int port = 4000; ++ int usbbus = -1; ++ int usbaddr = -1; ++ int usbvendor = -1; ++ int usbproduct = -1; ++ int on = 1; ++ int keepalive = -1; ++ char *ipv4_addr = NULL, *ipv6_addr = NULL; ++ union { ++ struct sockaddr_in v4; ++ struct sockaddr_in6 v6; ++ } serveraddr; ++ struct sigaction act; ++ libusb_device_handle *handle = NULL; ++ ++ while ((o = getopt_long(argc, argv, "hp:v:4:6:k:", longopts, NULL)) != -1) { ++ switch (o) { ++ case 'p': ++ port = strtol(optarg, &endptr, 10); ++ if (*endptr != '\0') { ++ fprintf(stderr, "Invalid value for --port: '%s'\n", optarg); ++ usage(1, argv[0]); ++ } ++ break; ++ case 'v': ++ verbose = strtol(optarg, &endptr, 10); ++ if (*endptr != '\0') { ++ fprintf(stderr, "Invalid value for --verbose: '%s'\n", optarg); ++ usage(1, argv[0]); ++ } ++ break; ++ case '4': ++ ipv4_addr = optarg; ++ break; ++ case '6': ++ ipv6_addr = optarg; ++ break; ++ case 'k': ++ keepalive = strtol(optarg, &endptr, 10); ++ if (*endptr != '\0') { ++ fprintf(stderr, "Invalid value for -k: '%s'\n", optarg); ++ usage(1, argv[0]); ++ } ++ break; ++ case '?': ++ case 'h': ++ usage(o == '?', argv[0]); ++ break; ++ } ++ } ++ if (optind == argc) { ++ fprintf(stderr, "Missing usb device identifier argument\n"); ++ usage(1, argv[0]); ++ } ++ delim = strchr(argv[optind], '-'); ++ if (delim && delim[1]) { ++ usbbus = strtol(argv[optind], &endptr, 10); ++ if (*endptr != '-') { ++ invalid_usb_device_id(argv[optind], argv[0]); ++ } ++ usbaddr = strtol(delim + 1, &endptr, 10); ++ if (*endptr != '\0') { ++ invalid_usb_device_id(argv[optind], argv[0]); ++ } ++ } else { ++ delim = strchr(argv[optind], ':'); ++ if (!delim || !delim[1]) { ++ invalid_usb_device_id(argv[optind], argv[0]); ++ } ++ usbvendor = strtol(argv[optind], &endptr, 16); ++ if (*endptr != ':' || usbvendor <= 0 || usbvendor > 0xffff) { ++ invalid_usb_device_id(argv[optind], argv[0]); ++ } ++ usbproduct = strtol(delim + 1, &endptr, 16); ++ /* Product ID 0000 is valid */ ++ if (*endptr != '\0' || usbproduct < 0 || usbproduct > 0xffff) { ++ invalid_usb_device_id(argv[optind], argv[0]); ++ } ++ } ++ optind++; ++ if (optind != argc) { ++ fprintf(stderr, "Excess non option arguments\n"); ++ usage(1, argv[0]); ++ } ++ ++ memset(&act, 0, sizeof(act)); ++ act.sa_handler = quit_handler; ++ sigaction(SIGINT, &act, NULL); ++ sigaction(SIGHUP, &act, NULL); ++ sigaction(SIGTERM, &act, NULL); ++ sigaction(SIGQUIT, &act, NULL); ++ ++ if (libusb_init(&ctx)) { ++ fprintf(stderr, "Could not init libusb\n"); ++ exit(1); ++ } ++ ++#if LIBUSB_API_VERSION >= 0x01000106 ++ libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, verbose); ++#else ++ libusb_set_debug(ctx, verbose); ++#endif ++ ++ if (ipv4_addr) { ++ server_fd = socket(AF_INET, SOCK_STREAM, 0); ++ } else { ++ server_fd = socket(AF_INET6, SOCK_STREAM, 0); ++ } ++ if (server_fd == -1) { ++ perror("Error creating ip socket"); ++ exit(1); ++ } ++ ++ if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) { ++ perror("Error setsockopt(SO_REUSEADDR) failed"); ++ exit(1); ++ } ++ ++ memset(&serveraddr, 0, sizeof(serveraddr)); ++ ++ if (ipv4_addr) { ++ serveraddr.v4.sin_family = AF_INET; ++ serveraddr.v4.sin_port = htons(port); ++ if ((inet_pton(AF_INET, ipv4_addr, ++ &serveraddr.v4.sin_addr)) != 1) { ++ perror("Error convert ipv4 address"); ++ exit(1); ++ } ++ } else { ++ serveraddr.v6.sin6_family = AF_INET6; ++ serveraddr.v6.sin6_port = htons(port); ++ if (ipv6_addr) { ++ if ((inet_pton(AF_INET6, ipv6_addr, ++ &serveraddr.v6.sin6_addr)) != 1) { ++ perror("Error convert ipv6 address"); ++ exit(1); ++ } ++ } else { ++ serveraddr.v6.sin6_addr = in6addr_any; ++ } ++ } ++ ++ if (bind(server_fd, (struct sockaddr *)&serveraddr, ++ sizeof(serveraddr))) { ++ perror("Error bind"); ++ exit(1); ++ } ++ ++ if (listen(server_fd, 1)) { ++ perror("Error listening"); ++ exit(1); ++ } ++ ++ while (running) { ++ client_fd = accept(server_fd, NULL, 0); ++ if (client_fd == -1) { ++ if (errno == EINTR) { ++ continue; ++ } ++ perror("accept"); ++ break; ++ } ++ ++ if (keepalive > 0) { ++ int optval = 1; ++ socklen_t optlen = sizeof(optval); ++ if (setsockopt(client_fd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) == -1) { ++ if (errno != ENOTSUP) { ++ perror("setsockopt SO_KEEPALIVE error."); ++ break; ++ } ++ } ++ optval = keepalive; /* set default TCP_KEEPIDLE time from cmdline */ ++ if (setsockopt(client_fd, SOL_TCP, TCP_KEEPIDLE, &optval, optlen) == -1) { ++ if (errno != ENOTSUP) { ++ perror("setsockopt TCP_KEEPIDLE error."); ++ break; ++ } ++ } ++ optval = 10; /* set default TCP_KEEPINTVL time as 10s */ ++ if (setsockopt(client_fd, SOL_TCP, TCP_KEEPINTVL, &optval, optlen) == -1) { ++ if (errno != ENOTSUP) { ++ perror("setsockopt TCP_KEEPINTVL error."); ++ break; ++ } ++ } ++ optval = 3; /* set default TCP_KEEPCNT as 3 */ ++ if (setsockopt(client_fd, SOL_TCP, TCP_KEEPCNT, &optval, optlen) == -1) { ++ if (errno != ENOTSUP) { ++ perror("setsockopt TCP_KEEPCNT error."); ++ break; ++ } ++ } ++ } ++ ++ flags = fcntl(client_fd, F_GETFL); ++ if (flags == -1) { ++ perror("fcntl F_GETFL"); ++ break; ++ } ++ flags = fcntl(client_fd, F_SETFL, flags | O_NONBLOCK); ++ if (flags == -1) { ++ perror("fcntl F_SETFL O_NONBLOCK"); ++ break; ++ } ++ ++ /* Try to find the specified usb device */ ++ if (usbvendor != -1) { ++ handle = libusb_open_device_with_vid_pid(ctx, usbvendor, ++ usbproduct); ++ if (!handle) { ++ fprintf(stderr, ++ "Could not open an usb-device with vid:pid %04x:%04x\n", ++ usbvendor, usbproduct); ++ } else if (verbose >= usbredirparser_info) { ++ libusb_device *dev; ++ dev = libusb_get_device(handle); ++ fprintf(stderr, "Open a usb-device with vid:pid %04x:%04x on " ++ "bus %03x device %03x\n", ++ usbvendor, usbproduct, ++ libusb_get_bus_number(dev), ++ libusb_get_device_address(dev)); ++ } ++ } else { ++ libusb_device **list = NULL; ++ ssize_t i, n; ++ ++ n = libusb_get_device_list(ctx, &list); ++ for (i = 0; i < n; i++) { ++ if (libusb_get_bus_number(list[i]) == usbbus && ++ libusb_get_device_address(list[i]) == usbaddr) ++ break; ++ } ++ if (i < n) { ++ if (libusb_open(list[i], &handle) != 0) { ++ fprintf(stderr, ++ "Could not open usb-device at busnum-devnum %d-%d\n", ++ usbbus, usbaddr); ++ } ++ } else { ++ fprintf(stderr, ++ "Could not find an usb-device at busnum-devnum %d-%d\n", ++ usbbus, usbaddr); ++ } ++ libusb_free_device_list(list, 1); ++ } ++ if (!handle) { ++ close(client_fd); ++ continue; ++ } ++ ++ host = usbredirhost_open(ctx, handle, usbredirserver_log, ++ usbredirserver_read, usbredirserver_write, ++ NULL, SERVER_VERSION, verbose, 0); ++ if (!host) ++ exit(1); ++ run_main_loop(); ++ usbredirhost_close(host); ++ handle = NULL; ++ } ++ ++ close(server_fd); ++ libusb_exit(ctx); ++ exit(0); ++} +-- +2.38.1 + diff --git a/SOURCES/0001-usbredirparser-Fix-unserialize-on-pristine-check.patch b/SOURCES/0001-usbredirparser-Fix-unserialize-on-pristine-check.patch deleted file mode 100644 index 9d21c34..0000000 --- a/SOURCES/0001-usbredirparser-Fix-unserialize-on-pristine-check.patch +++ /dev/null @@ -1,193 +0,0 @@ -From 6bf41a231b445ac5190c32e281b698b1ee5379b4 Mon Sep 17 00:00:00 2001 -From: Victor Toso -Date: Fri, 24 Jun 2022 23:29:08 +0200 -Subject: [PATCH 1/2] usbredirparser: Fix unserialize on pristine check -Content-type: text/plain - -As mentioned in the bug below, the user is trying to migrate QEMU and -it is failing on the unserialization of usbredirparser at the target -host. The user does not have USB attached to the VM at all. - -I've added a test that shows that serialization is currently broken. -It fails at the 'pristine' check in usbredirparser_unserialize(). - -This check was added with e37d86c "Skip empty write buffers when -unserializing parser" and restricted further with 186c4c7 "Avoid -memory leak from ill-formatted serialization data" - -The issue here is that usbredirparser's initialization sets some -fields and thus it isn't guaranteed to be pristine. - -The parser's basic data is: - - | write_buf_count ... : 1 - | write_buf ........ : 0xbc03e0 - | write_buf_total_size: 80 - | data ............. : (nil) - | header_read: ...... : 0 - | type_header_read .. : 0 - | data_read: ........ : 0 - -The current fix is to to ignore write_buf checks as, again, they are -not guaranteed to be pristine. usbredirparser library should properly -overwrite them when unserializing the data and if there were pending -buffers, they should be freed. - -Related: https://bugzilla.redhat.com/show_bug.cgi?id=2096008 - -Signed-off-by: Victor Toso ---- - tests/meson.build | 1 + - tests/serializer.c | 113 ++++++++++++++++++++++++++++++++ - usbredirparser/usbredirparser.c | 4 +- - 3 files changed, 115 insertions(+), 3 deletions(-) - create mode 100644 tests/serializer.c - -diff --git a/tests/meson.build b/tests/meson.build -index 0d4397b..2a179c9 100644 ---- a/tests/meson.build -+++ b/tests/meson.build -@@ -1,5 +1,6 @@ - tests = [ - 'filter', -+ 'serializer', - ] - - deps = dependency('glib-2.0') -diff --git a/tests/serializer.c b/tests/serializer.c -new file mode 100644 -index 0000000..4bd669e ---- /dev/null -+++ b/tests/serializer.c -@@ -0,0 +1,113 @@ -+/* -+ * Copyright 2022 Red Hat, Inc. -+ * -+ * This library is free software; you can redistribute it and/or -+ * modify it under the terms of the GNU Lesser General Public -+ * License as published by the Free Software Foundation; either -+ * version 2.1 of the License, or (at your option) any later version. -+ * -+ * This library is distributed in the hope that it will be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -+ * Lesser General Public License for more details. -+ * -+ * You should have received a copy of the GNU Lesser General Public -+ * License along with this library; if not, see . -+*/ -+#include "config.h" -+ -+#define G_LOG_DOMAIN "serializer" -+#define G_LOG_USE_STRUCTURED -+ -+#include "usbredirparser.h" -+ -+#include -+#include -+#include -+#include -+ -+ -+static void -+log_cb(void *priv, int level, const char *msg) -+{ -+ GLogLevelFlags glog_level; -+ -+ switch(level) { -+ case usbredirparser_error: -+ glog_level = G_LOG_LEVEL_ERROR; -+ break; -+ case usbredirparser_warning: -+ glog_level = G_LOG_LEVEL_WARNING; -+ break; -+ case usbredirparser_info: -+ glog_level = G_LOG_LEVEL_INFO; -+ break; -+ case usbredirparser_debug: -+ case usbredirparser_debug_data: -+ glog_level = G_LOG_LEVEL_DEBUG; -+ break; -+ default: -+ g_warn_if_reached(); -+ return; -+ } -+ g_log_structured(G_LOG_DOMAIN, glog_level, "MESSAGE", msg); -+} -+ -+static struct usbredirparser * -+get_usbredirparser(void) -+{ -+ struct usbredirparser *parser = usbredirparser_create(); -+ g_assert_nonnull(parser); -+ -+ uint32_t caps[USB_REDIR_CAPS_SIZE] = { 0, }; -+ /* Typical caps set by usbredirhost */ -+ usbredirparser_caps_set_cap(caps, usb_redir_cap_connect_device_version); -+ usbredirparser_caps_set_cap(caps, usb_redir_cap_filter); -+ usbredirparser_caps_set_cap(caps, usb_redir_cap_device_disconnect_ack); -+ usbredirparser_caps_set_cap(caps, usb_redir_cap_ep_info_max_packet_size); -+ usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids); -+ usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length); -+ usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_receiving); -+#if LIBUSBX_API_VERSION >= 0x01000103 -+ usbredirparser_caps_set_cap(caps, usb_redir_cap_bulk_streams); -+#endif -+ int parser_flags = usbredirparser_fl_usb_host; -+ -+ parser->log_func = log_cb; -+ usbredirparser_init(parser, -+ PACKAGE_STRING, -+ caps, -+ USB_REDIR_CAPS_SIZE, -+ parser_flags); -+ return parser; -+} -+ -+static void -+simple (gconstpointer user_data) -+{ -+ uint8_t *state = NULL; -+ int ret, len = -1; -+ -+ struct usbredirparser *source = get_usbredirparser(); -+ ret = usbredirparser_serialize(source, &state, &len); -+ g_assert_cmpint(ret, ==, 0); -+ -+ struct usbredirparser *target = get_usbredirparser(); -+ ret = usbredirparser_unserialize(target, state, len); -+ g_assert_cmpint(ret, ==, 0); -+ -+ g_clear_pointer(&state, free); -+ usbredirparser_destroy(source); -+ usbredirparser_destroy(target); -+} -+ -+int -+main(int argc, char **argv) -+{ -+ setlocale(LC_ALL, ""); -+ g_test_init(&argc, &argv, NULL); -+ -+ g_test_add_data_func("/serializer/serialize-and-unserialize", NULL, simple); -+ -+ return g_test_run(); -+} -diff --git a/usbredirparser/usbredirparser.c b/usbredirparser/usbredirparser.c -index cd1136b..a5dd0e7 100644 ---- a/usbredirparser/usbredirparser.c -+++ b/usbredirparser/usbredirparser.c -@@ -1816,9 +1816,7 @@ int usbredirparser_unserialize(struct usbredirparser *parser_pub, - return -1; - } - -- if (!(parser->write_buf_count == 0 && parser->write_buf == NULL && -- parser->write_buf_total_size == 0 && -- parser->data == NULL && parser->header_read == 0 && -+ if (!(parser->data == NULL && parser->header_read == 0 && - parser->type_header_read == 0 && parser->data_read == 0)) { - ERROR("unserialization must use a pristine parser"); - usbredirparser_assert_invariants(parser); --- -2.37.1 - diff --git a/SOURCES/0002-Use-typedef-on-redirect-structure-to-simplify-some-s.patch b/SOURCES/0002-Use-typedef-on-redirect-structure-to-simplify-some-s.patch new file mode 100644 index 0000000..2fc7ab3 --- /dev/null +++ b/SOURCES/0002-Use-typedef-on-redirect-structure-to-simplify-some-s.patch @@ -0,0 +1,135 @@ +From c1246d5d8332890df0dab7b29de86a42c2b7b36a Mon Sep 17 00:00:00 2001 +From: Frediano Ziglio +Date: Fri, 16 Sep 2022 20:14:28 +0100 +Subject: [PATCH 2/4] Use typedef on redirect structure to simplify some + statements + +Signed-off-by: Frediano Ziglio +--- + tools/usbredirect.c | 26 +++++++++++++------------- + 1 file changed, 13 insertions(+), 13 deletions(-) + +diff --git a/tools/usbredirect.c b/tools/usbredirect.c +index ff910ab..a479c55 100644 +--- a/tools/usbredirect.c ++++ b/tools/usbredirect.c +@@ -22,7 +22,7 @@ + #include + #endif + +-struct redirect { ++typedef struct redirect { + struct { + int vendor; + int product; +@@ -40,7 +40,7 @@ struct redirect { + int watch_server_id; + + GMainLoop *main_loop; +-}; ++} redirect; + + static bool + parse_opt_device(const char *device, int *vendor, int *product) +@@ -125,7 +125,7 @@ parse_opt_uri(const char *uri, char **adr, int *port) + return true; + } + +-static struct redirect * ++static redirect * + parse_opts(int *argc, char ***argv) + { + char *device = NULL; +@@ -133,7 +133,7 @@ parse_opts(int *argc, char ***argv) + char *localaddr = NULL; + gboolean keepalive = FALSE; + gint verbosity = 0; /* none */ +- struct redirect *self = NULL; ++ redirect *self = NULL; + + GOptionEntry entries[] = { + { "device", 0, 0, G_OPTION_ARG_STRING, &device, "Local USB device to be redirected", NULL }, +@@ -162,7 +162,7 @@ parse_opts(int *argc, char ***argv) + goto end; + } + +- self = g_new0(struct redirect, 1); ++ self = g_new0(redirect, 1); + if (!parse_opt_device(device, &self->device.vendor, &self->device.product)) { + g_printerr("Failed to parse device: '%s' - expected: vendor:product or busnum-devnum\n", device); + g_clear_pointer(&self, g_free); +@@ -202,7 +202,7 @@ end: + static gpointer + thread_handle_libusb_events(gpointer user_data) + { +- struct redirect *self = (struct redirect *) user_data; ++ redirect *self = (redirect *) user_data; + + int res = 0; + const char *desc = ""; +@@ -280,7 +280,7 @@ usbredir_log_cb(void *priv, int level, const char *msg) + static int + usbredir_read_cb(void *priv, uint8_t *data, int count) + { +- struct redirect *self = (struct redirect *) priv; ++ redirect *self = (redirect *) priv; + GIOStream *iostream = G_IO_STREAM(self->connection); + GError *err = NULL; + +@@ -308,7 +308,7 @@ usbredir_read_cb(void *priv, uint8_t *data, int count) + static int + usbredir_write_cb(void *priv, uint8_t *data, int count) + { +- struct redirect *self = (struct redirect *) priv; ++ redirect *self = (redirect *) priv; + GIOStream *iostream = G_IO_STREAM(self->connection); + GError *err = NULL; + +@@ -336,7 +336,7 @@ usbredir_write_cb(void *priv, uint8_t *data, int count) + static void + usbredir_write_flush_cb(void *user_data) + { +- struct redirect *self = (struct redirect *) user_data; ++ redirect *self = (redirect *) user_data; + if (!self || !self->usbredirhost) { + return; + } +@@ -387,7 +387,7 @@ usbredir_unlock_lock(void *user_data) + static gboolean + connection_handle_io_cb(GIOChannel *source, GIOCondition condition, gpointer user_data) + { +- struct redirect *self = (struct redirect *) user_data; ++ redirect *self = (redirect *) user_data; + + if (condition & G_IO_ERR || condition & G_IO_HUP) { + g_warning("Connection: err=%d, hup=%d - exiting", (condition & G_IO_ERR), (condition & G_IO_HUP)); +@@ -419,7 +419,7 @@ end: + static gboolean + signal_handler(gpointer user_data) + { +- struct redirect *self = (struct redirect *) user_data; ++ redirect *self = (redirect *) user_data; + g_main_loop_quit(self->main_loop); + return G_SOURCE_REMOVE; + } +@@ -431,7 +431,7 @@ connection_incoming_cb(GSocketService *service, + GObject *source_object, + gpointer user_data) + { +- struct redirect *self = (struct redirect *) user_data; ++ redirect *self = (redirect *) user_data; + self->connection = g_object_ref(client_connection); + + /* Add a GSource watch to handle polling for us and handle IO in the callback */ +@@ -456,7 +456,7 @@ main(int argc, char *argv[]) + goto err_init; + } + +- struct redirect *self = parse_opts(&argc, &argv); ++ redirect *self = parse_opts(&argc, &argv); + if (!self) { + /* specific issues logged in parse_opts() */ + return 1; +-- +2.39.0 + diff --git a/SOURCES/0002-usbredirparser-reset-parser-s-fields-on-unserialize.patch b/SOURCES/0002-usbredirparser-reset-parser-s-fields-on-unserialize.patch deleted file mode 100644 index c46aa22..0000000 --- a/SOURCES/0002-usbredirparser-reset-parser-s-fields-on-unserialize.patch +++ /dev/null @@ -1,63 +0,0 @@ -From b93c4cae1aebda786a478677d6364308e4579ade Mon Sep 17 00:00:00 2001 -From: Victor Toso -Date: Sat, 25 Jun 2022 00:29:12 +0200 -Subject: [PATCH 2/2] usbredirparser: reset parser's fields on unserialize -Content-type: text/plain - -This is a followup from previous commit and fixes the following leak. - - | 104 (24 direct, 80 indirect) bytes in 1 blocks are definitely lost in loss record 15 of 19 - | at 0x484A464: calloc (vg_replace_malloc.c:1328) - | by 0x485A238: usbredirparser_queue (usbredirparser.c:1235) - | by 0x485A571: usbredirparser_init (usbredirparser.c:227) - | by 0x40130B: get_usbredirparser (serializer.c:77) - | by 0x401379: simple (serializer.c:95) - | by 0x48FA3DD: ??? (in /usr/lib64/libglib-2.0.so.0.7200.2) - | by 0x48FA144: ??? (in /usr/lib64/libglib-2.0.so.0.7200.2) - | by 0x48FA8E1: g_test_run_suite (in /usr/lib64/libglib-2.0.so.0.7200.2) - | by 0x48FA94C: g_test_run (in /usr/lib64/libglib-2.0.so.0.7200.2) - | by 0x401161: main (serializer.c:112) - | - | LEAK SUMMARY: - | definitely lost: 24 bytes in 1 blocks - | indirectly lost: 80 bytes in 1 blocks - | possibly lost: 0 bytes in 0 blocks - | still reachable: 25,500 bytes in 17 blocks - | suppressed: 0 bytes in 0 blocks - | Reachable blocks (those to which a pointer was found) are not shown. - | To see them, rerun with: --leak-check=full --show-leak-kinds=all - -Signed-off-by: Victor Toso ---- - usbredirparser/usbredirparser.c | 15 +++++++++++++++ - 1 file changed, 15 insertions(+) - -diff --git a/usbredirparser/usbredirparser.c b/usbredirparser/usbredirparser.c -index a5dd0e7..9bfc27c 100644 ---- a/usbredirparser/usbredirparser.c -+++ b/usbredirparser/usbredirparser.c -@@ -1823,6 +1823,21 @@ int usbredirparser_unserialize(struct usbredirparser *parser_pub, - return -1; - } - -+ { -+ /* We need to reset parser's state to receive unserialized -+ * data. */ -+ struct usbredirparser_buf *wbuf = parser->write_buf; -+ while (wbuf) { -+ struct usbredirparser_buf *next_wbuf = wbuf->next; -+ free(wbuf->buf); -+ free(wbuf); -+ wbuf = next_wbuf; -+ } -+ parser->write_buf = NULL; -+ parser->write_buf_count = 0; -+ parser->write_buf_total_size = 0; -+ } -+ - if (unserialize_int(parser, &state, &remain, &i, "length")) { - usbredirparser_assert_invariants(parser); - return -1; --- -2.37.1 - diff --git a/SOURCES/0003-Factor-out-a-function-to-create-watches.patch b/SOURCES/0003-Factor-out-a-function-to-create-watches.patch new file mode 100644 index 0000000..7e27e1c --- /dev/null +++ b/SOURCES/0003-Factor-out-a-function-to-create-watches.patch @@ -0,0 +1,74 @@ +From 307747e2a73cf68a239ddd7b70333bbddf7f3e3b Mon Sep 17 00:00:00 2001 +From: Frediano Ziglio +Date: Fri, 16 Sep 2022 20:14:28 +0100 +Subject: [PATCH 3/4] Factor out a function to create watches + +--- + tools/usbredirect.c | 37 ++++++++++++++++++++----------------- + 1 file changed, 20 insertions(+), 17 deletions(-) + +diff --git a/tools/usbredirect.c b/tools/usbredirect.c +index a479c55..afe9dee 100644 +--- a/tools/usbredirect.c ++++ b/tools/usbredirect.c +@@ -415,6 +415,24 @@ end: + return G_SOURCE_REMOVE; + } + ++static void ++create_watch(redirect *self) ++{ ++ GSocket *socket = g_socket_connection_get_socket(self->connection); ++ int socket_fd = g_socket_get_fd(socket); ++ GIOChannel *io_channel = ++#ifdef G_OS_UNIX ++ g_io_channel_unix_new(socket_fd); ++#else ++ g_io_channel_win32_new_socket(socket_fd); ++#endif ++ ++ self->watch_server_id = g_io_add_watch(io_channel, ++ G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR, ++ connection_handle_io_cb, ++ self); ++} ++ + #ifdef G_OS_UNIX + static gboolean + signal_handler(gpointer user_data) +@@ -437,12 +455,7 @@ connection_incoming_cb(GSocketService *service, + /* Add a GSource watch to handle polling for us and handle IO in the callback */ + GSocket *connection_socket = g_socket_connection_get_socket(self->connection); + g_socket_set_keepalive(connection_socket, self->keepalive); +- int socket_fd = g_socket_get_fd(connection_socket); +- GIOChannel *io_channel = g_io_channel_unix_new(socket_fd); +- self->watch_server_id = g_io_add_watch(io_channel, +- G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR, +- connection_handle_io_cb, +- self); ++ create_watch(self); + return G_SOURCE_REMOVE; + } + +@@ -552,17 +565,7 @@ main(int argc, char *argv[]) + + GSocket *connection_socket = g_socket_connection_get_socket(self->connection); + g_socket_set_keepalive(connection_socket, self->keepalive); +- int socket_fd = g_socket_get_fd(connection_socket); +- GIOChannel *io_channel = +-#ifdef G_OS_UNIX +- g_io_channel_unix_new(socket_fd); +-#else +- g_io_channel_win32_new_socket(socket_fd); +-#endif +- self->watch_server_id = g_io_add_watch(io_channel, +- G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR, +- connection_handle_io_cb, +- self); ++ create_watch(self); + } else { + GSocketService *socket_service; + +-- +2.39.0 + diff --git a/SOURCES/0004-Recreate-watch-if-needed.patch b/SOURCES/0004-Recreate-watch-if-needed.patch new file mode 100644 index 0000000..91f3a34 --- /dev/null +++ b/SOURCES/0004-Recreate-watch-if-needed.patch @@ -0,0 +1,106 @@ +From 3fcbd4a2569f227ae6fad6a37c8864d33271e5f4 Mon Sep 17 00:00:00 2001 +From: Frediano Ziglio +Date: Sat, 17 Sep 2022 09:28:08 +0100 +Subject: [PATCH 4/4] Recreate watch if needed + +Do not always watch for output buffer. +Watching for output buffer if we don't have nothing to write +(which is the usual case) is consuming a lot of CPU. + +This fixes https://gitlab.freedesktop.org/spice/usbredir/-/issues/24. + +Signed-off-by: Frediano Ziglio +--- + tools/usbredirect.c | 28 ++++++++++++++++++++++++++-- + 1 file changed, 26 insertions(+), 2 deletions(-) + +diff --git a/tools/usbredirect.c b/tools/usbredirect.c +index afe9dee..59452aa 100644 +--- a/tools/usbredirect.c ++++ b/tools/usbredirect.c +@@ -29,6 +29,7 @@ typedef struct redirect { + } device; + bool is_client; + bool keepalive; ++ bool watch_inout; + char *addr; + int port; + int verbosity; +@@ -42,6 +43,8 @@ typedef struct redirect { + GMainLoop *main_loop; + } redirect; + ++static void create_watch(redirect *self); ++ + static bool + parse_opt_device(const char *device, int *vendor, int *product) + { +@@ -163,6 +166,7 @@ parse_opts(int *argc, char ***argv) + } + + self = g_new0(redirect, 1); ++ self->watch_inout = true; + if (!parse_opt_device(device, &self->device.vendor, &self->device.product)) { + g_printerr("Failed to parse device: '%s' - expected: vendor:product or busnum-devnum\n", device); + g_clear_pointer(&self, g_free); +@@ -277,6 +281,20 @@ usbredir_log_cb(void *priv, int level, const char *msg) + g_log_structured(G_LOG_DOMAIN, glog_level, "MESSAGE", msg); + } + ++static void ++update_watch(redirect *self) ++{ ++ const bool watch_inout = usbredirhost_has_data_to_write(self->usbredirhost) != 0; ++ if (watch_inout == self->watch_inout) { ++ return; ++ } ++ g_source_remove(self->watch_server_id); ++ self->watch_server_id = 0; ++ self->watch_inout = watch_inout; ++ ++ create_watch(self); ++} ++ + static int + usbredir_read_cb(void *priv, uint8_t *data, int count) + { +@@ -322,6 +340,7 @@ usbredir_write_cb(void *priv, uint8_t *data, int count) + if (g_error_matches(err, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { + /* Try again later */ + nbytes = 0; ++ update_watch(self); + } else { + if (err != NULL) { + g_warning("Failure at %s: %s", __func__, err->message); +@@ -401,13 +420,18 @@ connection_handle_io_cb(GIOChannel *source, GIOCondition condition, gpointer use + goto end; + } + } +- if (condition & G_IO_OUT) { ++ // try to write data in any case, to avoid having another iteration and ++ // creation of another watch if there is space in output buffer ++ if (usbredirhost_has_data_to_write(self->usbredirhost) != 0) { + int ret = usbredirhost_write_guest_data(self->usbredirhost); + if (ret < 0) { + g_critical("%s: Failed to write to guest", __func__); + goto end; + } + } ++ ++ // update the watch if needed ++ update_watch(self); + return G_SOURCE_CONTINUE; + + end: +@@ -428,7 +452,7 @@ create_watch(redirect *self) + #endif + + self->watch_server_id = g_io_add_watch(io_channel, +- G_IO_IN | G_IO_OUT | G_IO_HUP | G_IO_ERR, ++ G_IO_IN | G_IO_HUP | G_IO_ERR | (self->watch_inout ? G_IO_OUT : 0), + connection_handle_io_cb, + self); + } +-- +2.39.0 + diff --git a/SOURCES/0005-Add-documentation-examples-for-using-bus-device-iden.patch b/SOURCES/0005-Add-documentation-examples-for-using-bus-device-iden.patch new file mode 100644 index 0000000..7ccec20 --- /dev/null +++ b/SOURCES/0005-Add-documentation-examples-for-using-bus-device-iden.patch @@ -0,0 +1,51 @@ +From 79c3214ef403a801762a79702be20cf8a829e37b Mon Sep 17 00:00:00 2001 +From: John Call +Date: Mon, 19 Dec 2022 19:01:50 +0000 +Subject: [PATCH 5/7] Add documentation examples for using bus-device + identification + +--- + tools/usbredirect.1 | 6 ++++++ + tools/usbredirect.c | 2 +- + 2 files changed, 7 insertions(+), 1 deletion(-) + +diff --git a/tools/usbredirect.1 b/tools/usbredirect.1 +index d6b2a63..2be8be0 100644 +--- a/tools/usbredirect.1 ++++ b/tools/usbredirect.1 +@@ -4,6 +4,9 @@ usbredirect \- exporting an USB device for use from another (virtual) machine + .SH SYNOPSIS + .B usbredirect + [\fI--device vendor:product\fR] [\fI--to addr:port\fR] [\fI--as addr:port\fR] ++.br ++.B usbredirect ++[\fI--device bus-device\fR] [\fI--to addr:port\fR] [\fI--as addr:port\fR] + .SH DESCRIPTION + usbredirect is an usbredir client for exporting an USB device either as TCP + client or server, for use from another (virtual) machine through the usbredir +@@ -11,6 +14,9 @@ protocol. + .PP + You can specify the USB device to export by USB id in the form of + \fI:\fR. ++.br ++Or you can specify the USB device to export in the form of ++\fI-\fR. + .PP + Notice that an instance of usbredirect can only be used to export a single USB + device and it will close once the other side closes the connection. If you +diff --git a/tools/usbredirect.c b/tools/usbredirect.c +index 59452aa..88c553b 100644 +--- a/tools/usbredirect.c ++++ b/tools/usbredirect.c +@@ -139,7 +139,7 @@ parse_opts(int *argc, char ***argv) + redirect *self = NULL; + + GOptionEntry entries[] = { +- { "device", 0, 0, G_OPTION_ARG_STRING, &device, "Local USB device to be redirected", NULL }, ++ { "device", 0, 0, G_OPTION_ARG_STRING, &device, "Local USB device to be redirected identified as either VENDOR:PRODUCT \"0123:4567\" or BUS-DEVICE \"5-2\"", NULL }, + { "to", 0, 0, G_OPTION_ARG_STRING, &remoteaddr, "Client URI to connect to", NULL }, + { "as", 0, 0, G_OPTION_ARG_STRING, &localaddr, "Server URI to be run", NULL }, + { "keepalive", 'k', 0, G_OPTION_ARG_NONE, &keepalive, "If we should set SO_KEEPALIVE flag on underlying socket", NULL }, +-- +2.39.0 + diff --git a/SOURCES/0006-usbredirect-allow-multiple-devices-by-vendor-product.patch b/SOURCES/0006-usbredirect-allow-multiple-devices-by-vendor-product.patch new file mode 100644 index 0000000..382bd4c --- /dev/null +++ b/SOURCES/0006-usbredirect-allow-multiple-devices-by-vendor-product.patch @@ -0,0 +1,136 @@ +From ebfcffbee055ef5ddf981b77240223790dcc0140 Mon Sep 17 00:00:00 2001 +From: Victor Toso +Date: Thu, 22 Dec 2022 16:13:08 +0100 +Subject: [PATCH 6/7] usbredirect: allow multiple devices by vendor:product + +Currently, if an user tries to redirect two devices with the same +vendor:product info, the second instance of usbredirect will not +succeed, leading to a segmentation fault. + +The core of the problem is that usbredirect is using +libusb_open_device_with_vid_pid() which always returns the first +instance of a given vendor:product, leading the second instance of +usbredirect to give an usb device that is in use to usbredirhost. + +This patch fixes the situation, making it possible to run usbredirect +with --device $vendor:$product multiple times. We do a early check +that we can claim the usb device, prior to handle it over to +usbredirhost. + +Related: https://gitlab.freedesktop.org/spice/usbredir/-/issues/29 +Signed-off-by: Victor Toso +--- + tools/usbredirect.c | 90 ++++++++++++++++++++++++++++++++++++++++++--- + 1 file changed, 85 insertions(+), 5 deletions(-) + +diff --git a/tools/usbredirect.c b/tools/usbredirect.c +index 88c553b..78fe5c2 100644 +--- a/tools/usbredirect.c ++++ b/tools/usbredirect.c +@@ -467,6 +467,90 @@ signal_handler(gpointer user_data) + } + #endif + ++static bool ++can_claim_usb_device(libusb_device *dev, libusb_device_handle **handle) ++{ ++ int ret = libusb_open(dev, handle); ++ if (ret != 0) { ++ g_debug("Failed to open device"); ++ return false; ++ } ++ ++ /* Opening is not enough. We need to check if device can be claimed ++ * for I/O operations */ ++ struct libusb_config_descriptor *config = NULL; ++ ret = libusb_get_active_config_descriptor(dev, &config); ++ if (ret != 0 || config == NULL) { ++ g_debug("Failed to get active descriptor"); ++ *handle = NULL; ++ return false; ++ } ++ ++#if LIBUSBX_API_VERSION >= 0x01000102 ++ libusb_set_auto_detach_kernel_driver(*handle, 1); ++#endif ++ ++ int i; ++ for (i = 0; i < config->bNumInterfaces; i++) { ++ int interface_num = config->interface[i].altsetting[0].bInterfaceNumber; ++#if LIBUSBX_API_VERSION < 0x01000102 ++ ret = libusb_detach_kernel_driver(handle, interface_num); ++ if (ret != 0 && ret != LIBUSB_ERROR_NOT_FOUND ++ && ret != LIBUSB_ERROR_NOT_SUPPORTED) { ++ g_error("failed to detach driver from interface %d: %s", ++ interface_num, libusb_error_name(ret)); ++ *handle = NULL; ++ break ++ } ++#endif ++ ret = libusb_claim_interface(*handle, interface_num); ++ if (ret != 0) { ++ g_debug("Could not claim interface"); ++ *handle = NULL; ++ break; ++ } ++ ret = libusb_release_interface(*handle, interface_num); ++ if (ret != 0) { ++ g_debug("Could not release interface"); ++ *handle = NULL; ++ break; ++ } ++ } ++ ++ libusb_free_config_descriptor(config); ++ return *handle != NULL; ++} ++ ++static libusb_device_handle * ++open_usb_device(redirect *self) ++{ ++ struct libusb_device **devs; ++ struct libusb_device_handle *dev_handle = NULL; ++ size_t i, ndevices; ++ ++ ndevices = libusb_get_device_list(NULL, &devs); ++ for (i = 0; i < ndevices; i++) { ++ struct libusb_device_descriptor desc; ++ if (libusb_get_device_descriptor(devs[i], &desc) != 0) { ++ g_warning("Failed to get descriptor"); ++ continue; ++ } ++ ++ if (self->device.vendor != desc.idVendor || ++ self->device.product != desc.idProduct) { ++ continue; ++ } ++ ++ if (can_claim_usb_device(devs[i], &dev_handle)) { ++ break; ++ } ++ } ++ ++ libusb_free_device_list(devs, 1); ++ return dev_handle; ++} ++ ++ + static gboolean + connection_incoming_cb(GSocketService *service, + GSocketConnection *client_connection, +@@ -516,11 +600,7 @@ main(int argc, char *argv[]) + g_unix_signal_add(SIGTERM, signal_handler, self); + #endif + +- /* This is binary is not meant to support plugins so it is safe to pass +- * NULL as libusb_context here and all subsequent calls */ +- libusb_device_handle *device_handle = libusb_open_device_with_vid_pid(NULL, +- self->device.vendor, +- self->device.product); ++ libusb_device_handle *device_handle = open_usb_device(self); + if (!device_handle) { + g_printerr("Failed to open device!\n"); + goto err_init; +-- +2.39.0 + diff --git a/SOURCES/0007-usbredirect-use-the-correct-bus-device.patch b/SOURCES/0007-usbredirect-use-the-correct-bus-device.patch new file mode 100644 index 0000000..c5dd7e9 --- /dev/null +++ b/SOURCES/0007-usbredirect-use-the-correct-bus-device.patch @@ -0,0 +1,145 @@ +From c2fc30ec2b424ac6e45e45f756a2559848bd3116 Mon Sep 17 00:00:00 2001 +From: Victor Toso +Date: Thu, 22 Dec 2022 16:58:43 +0100 +Subject: [PATCH 7/7] usbredirect: use the correct bus-device + +This patch is a continuation from: +"usbredirect: allow multiple devices by vendor:product" + +As we were using libusb_open_device_with_vid_pid(), if an user +requested that device on 2-3 was redirected, we would instead get the +vendor and product info of device on 2-3 and use that info to pick a +usb device. This is wrong when multiple devices that shared +vendor:product are plugged as libbusb_open_device_with_vid_pid() +always return the same (first) device. + +This commit now stores bus-device info and uses that to pick the usb +device to redirect. + +Related: https://gitlab.freedesktop.org/spice/usbredir/-/issues/29 +Signed-off-by: Victor Toso +--- + tools/usbredirect.c | 61 +++++++++++++++++++-------------------------- + 1 file changed, 26 insertions(+), 35 deletions(-) + +diff --git a/tools/usbredirect.c b/tools/usbredirect.c +index 78fe5c2..0451dda 100644 +--- a/tools/usbredirect.c ++++ b/tools/usbredirect.c +@@ -24,9 +24,14 @@ + + typedef struct redirect { + struct { ++ /* vendor:product */ + int vendor; + int product; ++ /* bus-device */ ++ int bus; ++ int device_number; + } device; ++ bool by_bus; + bool is_client; + bool keepalive; + bool watch_inout; +@@ -46,7 +51,7 @@ typedef struct redirect { + static void create_watch(redirect *self); + + static bool +-parse_opt_device(const char *device, int *vendor, int *product) ++parse_opt_device(redirect *self, const char *device) + { + if (!device) { + g_warning("No device to redirect. For testing only\n"); +@@ -54,38 +59,15 @@ parse_opt_device(const char *device, int *vendor, int *product) + } + + if (g_strrstr(device, "-") != NULL) { +- /* Get vendor and product by bus and address number */ ++ self->by_bus = true; + char **usbid = g_strsplit(device, "-", 2); + if (usbid == NULL || usbid[0] == NULL || usbid[1] == NULL || usbid[2] != NULL) { + g_strfreev(usbid); + return false; + } +- gint64 bus = g_ascii_strtoll(usbid[0], NULL, 10); +- gint64 addr = g_ascii_strtoll(usbid[1], NULL, 10); ++ self->device.bus = g_ascii_strtoll(usbid[0], NULL, 10); ++ self->device.device_number = g_ascii_strtoll(usbid[1], NULL, 10); + g_strfreev(usbid); +- +- libusb_device **list = NULL; +- ssize_t i, n; +- +- n = libusb_get_device_list(NULL, &list); +- for (i = 0; i < n; i++) { +- if (libusb_get_bus_number(list[i]) == bus && +- libusb_get_device_address(list[i]) == addr) { +- break; +- } +- } +- +- if (i == n) { +- libusb_free_device_list(list, true); +- return false; +- } +- +- struct libusb_device_descriptor desc; +- libusb_get_device_descriptor(list[i], &desc); +- *vendor = desc.idVendor; +- *product = desc.idProduct; +- +- libusb_free_device_list(list, true); + return true; + } + +@@ -95,12 +77,14 @@ parse_opt_device(const char *device, int *vendor, int *product) + return false; + } + +- *vendor = g_ascii_strtoll(usbid[0], NULL, 16); +- *product = g_ascii_strtoll(usbid[1], NULL, 16); ++ self->device.vendor = g_ascii_strtoll(usbid[0], NULL, 16); ++ self->device.product = g_ascii_strtoll(usbid[1], NULL, 16); + g_strfreev(usbid); + +- if (*vendor <= 0 || *vendor > 0xffff || *product < 0 || *product > 0xffff) { +- g_printerr("Bad vendor:product values %04x:%04x", *vendor, *product); ++ if (self->device.vendor <= 0 || self->device.vendor > 0xffff || ++ self->device.product < 0 || self->device.product > 0xffff) { ++ g_printerr("Bad vendor:product values %04x:%04x", ++ self->device.vendor, self->device.product); + return false; + } + +@@ -167,7 +151,7 @@ parse_opts(int *argc, char ***argv) + + self = g_new0(redirect, 1); + self->watch_inout = true; +- if (!parse_opt_device(device, &self->device.vendor, &self->device.product)) { ++ if (!parse_opt_device(self, device)) { + g_printerr("Failed to parse device: '%s' - expected: vendor:product or busnum-devnum\n", device); + g_clear_pointer(&self, g_free); + goto end; +@@ -536,9 +520,16 @@ open_usb_device(redirect *self) + continue; + } + +- if (self->device.vendor != desc.idVendor || +- self->device.product != desc.idProduct) { +- continue; ++ if (self->by_bus && ++ (self->device.bus != libusb_get_bus_number(devs[i]) || ++ self->device.device_number != libusb_get_device_address(devs[i]))) { ++ continue; ++ } ++ ++ if (!self->by_bus && ++ (self->device.vendor != desc.idVendor || ++ self->device.product != desc.idProduct)) { ++ continue; + } + + if (can_claim_usb_device(devs[i], &dev_handle)) { +-- +2.39.0 + diff --git a/SPECS/usbredir.spec b/SPECS/usbredir.spec index c4969cc..bd4e5fe 100644 --- a/SPECS/usbredir.spec +++ b/SPECS/usbredir.spec @@ -1,12 +1,17 @@ Name: usbredir -Version: 0.12.0 -Release: 3%{?dist} +Version: 0.13.0 +Release: 2%{?dist} Summary: USB network redirection protocol libraries License: LGPLv2+ URL: https://spice-space.org/usbredir.html Source0: http://spice-space.org/download/%{name}/%{name}-%{version}.tar.xz -Patch0001: 0001-usbredirparser-Fix-unserialize-on-pristine-check.patch -Patch0002: 0002-usbredirparser-reset-parser-s-fields-on-unserialize.patch +Patch0001: 0001-Revert-remove-usbredirserver.patch +Patch0002: 0002-Use-typedef-on-redirect-structure-to-simplify-some-s.patch +Patch0003: 0003-Factor-out-a-function-to-create-watches.patch +Patch0004: 0004-Recreate-watch-if-needed.patch +Patch0005: 0005-Add-documentation-examples-for-using-bus-device-iden.patch +Patch0006: 0006-usbredirect-allow-multiple-devices-by-vendor-product.patch +Patch0007: 0007-usbredirect-use-the-correct-bus-device.patch BuildRequires: gcc BuildRequires: glib2-devel BuildRequires: libusb1-devel >= 1.0.9 @@ -86,6 +91,17 @@ A simple USB host TCP server, using libusbredirhost. %changelog +* Thu Jan 05 2023 Victor Toso - 0.13.0-2 +- Fixes 100% CPU usage when usbredirect used as TCP server + Related: rhbz#2157520 +- Fixes USB redirection of identical devices + Resolves: rhbz#2157520 + +* Wed Nov 30 2022 Victor Toso - 0.13.0-1 +- Rebase to latest upstream: 0.13.0 +- Keeps usbredirserver binary (removed upstream) + Related: rhbz#2135760 + * Thu Jul 28 2022 Victor Toso - 0.12.0-3 - Fix unserialization (migration regression) Related: rhbz#2111368