From 9722d5fd2fc4814d57f69c44e942dc050f55589d Mon Sep 17 00:00:00 2001 From: Volodymyr Samokhatko Date: Wed, 15 Feb 2023 15:00:47 +0100 Subject: [PATCH 1/6] threading: recursive mutex --- include/rfb/threading.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/include/rfb/threading.h b/include/rfb/threading.h index 2a497814b..fefb7cc13 100644 --- a/include/rfb/threading.h +++ b/include/rfb/threading.h @@ -26,11 +26,20 @@ #ifdef LIBVNCSERVER_HAVE_LIBPTHREAD #include +#define RFB_THREADING_GLUE_DETAIL(x, y) x ## y +#define RFB_THREADING_GLUE(x, y) RFB_THREADING_GLUE_DETAIL(x, y) #if 0 /* debugging */ #define LOCK(mutex) (rfbLog("%s:%d LOCK(%s,0x%x)\n",__FILE__,__LINE__,#mutex,&(mutex)), pthread_mutex_lock(&(mutex))) #define UNLOCK(mutex) (rfbLog("%s:%d UNLOCK(%s,0x%x)\n",__FILE__,__LINE__,#mutex,&(mutex)), pthread_mutex_unlock(&(mutex))) #define MUTEX(mutex) pthread_mutex_t (mutex) #define INIT_MUTEX(mutex) (rfbLog("%s:%d INIT_MUTEX(%s,0x%x)\n",__FILE__,__LINE__,#mutex,&(mutex)), pthread_mutex_init(&(mutex),NULL)) +#define INIT_MUTEX_RECURSIVE(mutex) do { \ + rfbLog("%s:%d INIT_MUTEX_RECURSIVE(%s,0x%x)\n",__FILE__,__LINE__,#mutex,&(mutex); \ + pthread_mutexattr_t RFB_THREADING_GLUE(recattr, __LINE__); \ + pthread_mutexattr_settype(&RFB_THREADING_GLUE(recattr, __LINE__), PTHREAD_MUTEX_RECURSIVE); \ + pthread_mutex_init(&(mutex), &RFB_THREADING_GLUE(recattr, __LINE__)); \ + pthread_mutexattr_destroy(&RFB_THREADING_GLUE(recattr, __LINE__)); \ + } while (0) #define TINI_MUTEX(mutex) (rfbLog("%s:%d TINI_MUTEX(%s)\n",__FILE__,__LINE__,#mutex), pthread_mutex_destroy(&(mutex))) #define TSIGNAL(cond) (rfbLog("%s:%d TSIGNAL(%s)\n",__FILE__,__LINE__,#cond), pthread_cond_signal(&(cond))) #define WAIT(cond,mutex) (rfbLog("%s:%d WAIT(%s,%s)\n",__FILE__,__LINE__,#cond,#mutex), pthread_cond_wait(&(cond),&(mutex))) @@ -46,6 +55,13 @@ #define MUTEX(mutex) pthread_mutex_t (mutex) #define MUTEX_SIZE (sizeof(pthread_mutex_t)) #define INIT_MUTEX(mutex) pthread_mutex_init(&(mutex),NULL) +#define INIT_MUTEX_RECURSIVE(mutex) do { \ + pthread_mutexattr_t RFB_THREADING_GLUE(recattr, __LINE__); \ + pthread_mutexattr_init(&RFB_THREADING_GLUE(recattr, __LINE__)); \ + pthread_mutexattr_settype(&RFB_THREADING_GLUE(recattr, __LINE__), PTHREAD_MUTEX_RECURSIVE); \ + pthread_mutex_init(&(mutex), &RFB_THREADING_GLUE(recattr, __LINE__)); \ + pthread_mutexattr_destroy(&RFB_THREADING_GLUE(recattr, __LINE__)); \ + } while (0) #define TINI_MUTEX(mutex) pthread_mutex_destroy(&(mutex)) #define TSIGNAL(cond) pthread_cond_signal(&(cond)) #define WAIT(cond,mutex) pthread_cond_wait(&(cond),&(mutex)) @@ -66,6 +82,7 @@ #define MUTEX(mutex) CRITICAL_SECTION (mutex) #define MUTEX_SIZE (sizeof(CRITICAL_SECTION)) #define INIT_MUTEX(mutex) InitializeCriticalSection(&(mutex)) +#define INIT_MUTEX_RECURSIVE(mutex) INIT_MUTEX(mutex) #define TINI_MUTEX(mutex) DeleteCriticalSection(&(mutex)) #define TSIGNAL(cond) WakeAllConditionVariable(&(cond)) #define WAIT(cond,mutex) SleepConditionVariableCS(&(cond),&(mutex),INFINITE); @@ -83,6 +100,7 @@ #define UNLOCK(mutex) #define MUTEX(mutex) #define INIT_MUTEX(mutex) +#define INIT_MUTEX_RECURSIVE(mutex) #define TINI_MUTEX(mutex) #define TSIGNAL(cond) #define WAIT(cond,mutex) this_is_unsupported From d306fef6436ebca4f23bfa90b810b089661f56c1 Mon Sep 17 00:00:00 2001 From: Volodymyr Samokhatko Date: Thu, 16 Mar 2023 18:33:49 +0100 Subject: [PATCH 2/6] libvncserver: linked lists --- CMakeLists.txt | 1 + include/rfb/rfblist.h | 495 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 496 insertions(+) create mode 100644 include/rfb/rfblist.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e391f422..e3fff0037 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -783,6 +783,7 @@ if(LIBVNCSERVER_INSTALL) include/rfb/rfb.h include/rfb/rfbclient.h ${CMAKE_CURRENT_BINARY_DIR}/include/rfb/rfbconfig.h + include/rfb/rfblist.h include/rfb/rfbproto.h include/rfb/rfbregion.h ) diff --git a/include/rfb/rfblist.h b/include/rfb/rfblist.h new file mode 100644 index 000000000..ea981a1dd --- /dev/null +++ b/include/rfb/rfblist.h @@ -0,0 +1,495 @@ +/* + * Copyright © 2010 Intel Corporation + * Copyright © 2010 Francisco Jerez + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + * + */ + +#ifndef _RFB_LIST_H_ +#define _RFB_LIST_H_ + +#include /* offsetof() */ + +/** + * @file Classic doubly-link circular list implementation. + * For real usage examples of the linked list, see the file test/list.c + * + * Example: + * We need to keep a list of struct foo in the parent struct bar, i.e. what + * we want is something like this. + * + * struct bar { + * ... + * struct foo *list_of_foos; -----> struct foo {}, struct foo {}, struct foo{} + * ... + * } + * + * We need one list head in bar and a list element in all list_of_foos (both are of + * data type 'struct rfb_list'). + * + * struct bar { + * ... + * struct rfb_list list_of_foos; + * ... + * } + * + * struct foo { + * ... + * struct rfb_list entry; + * ... + * } + * + * Now we initialize the list head: + * + * struct bar bar; + * ... + * rfb_list_init(&bar.list_of_foos); + * + * Then we create the first element and add it to this list: + * + * struct foo *foo = malloc(...); + * .... + * rfb_list_add(&foo->entry, &bar.list_of_foos); + * + * Repeat the above for each element you want to add to the list. Deleting + * works with the element itself. + * rfb_list_del(&foo->entry); + * free(foo); + * + * Note: calling rfb_list_del(&bar.list_of_foos) will set bar.list_of_foos to an empty + * list again. + * + * Looping through the list requires a 'struct foo' as iterator and the + * name of the field the subnodes use. + * + * struct foo *iterator; + * rfb_list_for_each_entry(iterator, &bar.list_of_foos, entry) { + * if (iterator->something == ...) + * ... + * } + * + * Note: You must not call rfb_list_del() on the iterator if you continue the + * loop. You need to run the safe for-each loop instead: + * + * struct foo *iterator, *next; + * rfb_list_for_each_entry_safe(iterator, next, &bar.list_of_foos, entry) { + * if (...) + * rfb_list_del(&iterator->entry); + * } + * + */ + +/** + * The linkage struct for list nodes. This struct must be part of your + * to-be-linked struct. struct rfb_list is required for both the head of the + * list and for each list node. + * + * Position and name of the struct rfb_list field is irrelevant. + * There are no requirements that elements of a list are of the same type. + * There are no requirements for a list head, any struct rfb_list can be a list + * head. + */ +struct rfb_list { + struct rfb_list *next, *prev; +}; + +/** + * Initialize the list as an empty list. + * + * Example: + * rfb_list_init(&bar->list_of_foos); + * + * @param list The list to initialize + */ +static inline void +rfb_list_init(struct rfb_list *list) +{ + list->next = list->prev = list; +} + +static inline void +__rfb_list_add(struct rfb_list *entry, + struct rfb_list *prev, struct rfb_list *next) +{ + next->prev = entry; + entry->next = next; + entry->prev = prev; + prev->next = entry; +} + +/** + * Insert a new element after the given list head. The new element does not + * need to be initialised as empty list. + * The list changes from: + * head → some element → ... + * to + * head → new element → older element → ... + * + * Example: + * struct foo *newfoo = malloc(...); + * rfb_list_add(&newfoo->entry, &bar->list_of_foos); + * + * @param entry The new element to prepend to the list. + * @param head The existing list. + */ +static inline void +rfb_list_add(struct rfb_list *entry, struct rfb_list *head) +{ + __rfb_list_add(entry, head, head->next); +} + +/** + * Append a new element to the end of the list given with this list head. + * + * The list changes from: + * head → some element → ... → lastelement + * to + * head → some element → ... → lastelement → new element + * + * Example: + * struct foo *newfoo = malloc(...); + * rfb_list_append(&newfoo->entry, &bar->list_of_foos); + * + * @param entry The new element to prepend to the list. + * @param head The existing list. + */ +static inline void +rfb_list_append(struct rfb_list *entry, struct rfb_list *head) +{ + __rfb_list_add(entry, head->prev, head); +} + +static inline void +__rfb_list_del(struct rfb_list *prev, struct rfb_list *next) +{ + next->prev = prev; + prev->next = next; +} + +/** + * Remove the element from the list it is in. Using this function will reset + * the pointers to/from this element so it is removed from the list. It does + * NOT free the element itself or manipulate it otherwise. + * + * Using rfb_list_del on a pure list head (like in the example at the top of + * this file) will NOT remove the first element from + * the list but rather reset the list as empty list. + * + * Example: + * rfb_list_del(&foo->entry); + * + * @param entry The element to remove. + */ +static inline void +rfb_list_del(struct rfb_list *entry) +{ + __rfb_list_del(entry->prev, entry->next); + rfb_list_init(entry); +} + +/** + * Check if the list is empty. + * + * Example: + * rfb_list_is_empty(&bar->list_of_foos); + * + * @return True if the list is empty or False if the list contains one or more + * elements. + */ +static inline int +rfb_list_is_empty(struct rfb_list *head) +{ + return head->next == head; +} + +/** + * Returns a pointer to the container of this list element. + * + * Example: + * struct foo* f; + * f = container_of(&foo->entry, struct foo, entry); + * assert(f == foo); + * + * @param ptr Pointer to the struct rfb_list. + * @param type Data type of the list element. + * @param member Member name of the struct rfb_list field in the list element. + * @return A pointer to the data struct containing the list head. + */ +#ifndef container_of +#define container_of(ptr, type, member) \ + (type *)((char *)(ptr) - offsetof(type, member)) +#endif + +/** + * Alias of container_of + */ +#define rfb_list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * Retrieve the first list entry for the given list pointer. + * + * Example: + * struct foo *first; + * first = rfb_list_first_entry(&bar->list_of_foos, struct foo, list_of_foos); + * + * @param ptr The list head + * @param type Data type of the list element to retrieve + * @param member Member name of the struct rfb_list field in the list element. + * @return A pointer to the first list element. + */ +#define rfb_list_first_entry(ptr, type, member) \ + rfb_list_entry((ptr)->next, type, member) + +/** + * Retrieve the last list entry for the given listpointer. + * + * Example: + * struct foo *first; + * first = rfb_list_last_entry(&bar->list_of_foos, struct foo, list_of_foos); + * + * @param ptr The list head + * @param type Data type of the list element to retrieve + * @param member Member name of the struct rfb_list field in the list element. + * @return A pointer to the last list element. + */ +#define rfb_list_last_entry(ptr, type, member) \ + rfb_list_entry((ptr)->prev, type, member) + +#ifdef HAVE_TYPEOF +#define __container_of(ptr, sample, member) \ + container_of(ptr, typeof(*sample), member) +#else +/* This implementation of __container_of has undefined behavior according + * to the C standard, but it works in many cases. If your compiler doesn't + * support typeof() and fails with this implementation, please try a newer + * compiler. + */ +#define __container_of(ptr, sample, member) \ + (void *)((char *)(ptr) \ + - ((char *)&(sample)->member - (char *)(sample))) +#endif + +/** + * Loop through the list given by head and set pos to struct in the list. + * + * Example: + * struct foo *iterator; + * rfb_list_for_each_entry(iterator, &bar->list_of_foos, entry) { + * [modify iterator] + * } + * + * This macro is not safe for node deletion. Use rfb_list_for_each_entry_safe + * instead. + * + * @param pos Iterator variable of the type of the list elements. + * @param head List head + * @param member Member name of the struct rfb_list in the list elements. + * + */ +#define rfb_list_for_each_entry(pos, head, member) \ + for (pos = NULL, \ + pos = __container_of((head)->next, pos, member); \ + &pos->member != (head); \ + pos = __container_of(pos->member.next, pos, member)) + +/** + * Loop through the list, keeping a backup pointer to the element. This + * macro allows for the deletion of a list element while looping through the + * list. + * + * See rfb_list_for_each_entry for more details. + */ +#define rfb_list_for_each_entry_safe(pos, tmp, head, member) \ + for (pos = NULL, \ + pos = __container_of((head)->next, pos, member), \ + tmp = __container_of(pos->member.next, pos, member); \ + &pos->member != (head); \ + pos = tmp, tmp = __container_of(pos->member.next, tmp, member)) + +/* NULL-Terminated List Interface + * + * The interface below does _not_ use the struct rfb_list as described above. + * It is mainly for legacy structures that cannot easily be switched to + * struct rfb_list. + * + * This interface is for structs like + * struct foo { + * [...] + * struct foo *next; + * [...] + * }; + * + * The position and field name of "next" are arbitrary. + */ + +/** + * Init the element as null-terminated list. + * + * Example: + * struct foo *list = malloc(); + * nt_list_init(list, next); + * + * @param list The list element that will be the start of the list + * @param member Member name of the field pointing to next struct + */ +#define nt_list_init(_list, _member) \ + (_list)->_member = NULL + +/** + * Returns the next element in the list or NULL on termination. + * + * Example: + * struct foo *element = list; + * while ((element = nt_list_next(element, next)) { } + * + * This macro is not safe for node deletion. Use nt_list_for_each_entry_safe + * instead. + * + * @param list The list or current element. + * @param member Member name of the field pointing to next struct. + */ +#define nt_list_next(_list, _member) \ + (_list)->_member + +/** + * Iterate through each element in the list. + * + * Example: + * struct foo *iterator; + * nt_list_for_each_entry(iterator, list, next) { + * [modify iterator] + * } + * + * @param entry Assigned to the current list element + * @param list The list to iterate through. + * @param member Member name of the field pointing to next struct. + */ +#define nt_list_for_each_entry(_entry, _list, _member) \ + for (_entry = _list; _entry; _entry = (_entry)->_member) + +/** + * Iterate through each element in the list, keeping a backup pointer to the + * element. This macro allows for the deletion of a list element while + * looping through the list. + * + * See nt_list_for_each_entry for more details. + * + * @param entry Assigned to the current list element + * @param tmp The pointer to the next element + * @param list The list to iterate through. + * @param member Member name of the field pointing to next struct. + */ +#define nt_list_for_each_entry_safe(_entry, _tmp, _list, _member) \ + for (_entry = _list, _tmp = (_entry) ? (_entry)->_member : NULL;\ + _entry; \ + _entry = _tmp, _tmp = (_tmp) ? (_tmp)->_member: NULL) + +/** + * Append the element to the end of the list. This macro may be used to + * merge two lists. + * + * Example: + * struct foo *elem = malloc(...); + * nt_list_init(elem, next) + * nt_list_append(elem, list, struct foo, next); + * + * Resulting list order: + * list_item_0 -> list_item_1 -> ... -> elem_item_0 -> elem_item_1 ... + * + * @param entry An entry (or list) to append to the list + * @param list The list to append to. This list must be a valid list, not + * NULL. + * @param type The list type + * @param member Member name of the field pointing to next struct + */ +#define nt_list_append(_entry, _list, _type, _member) \ + do { \ + _type *__iterator = _list; \ + while (__iterator->_member) { __iterator = __iterator->_member;}\ + __iterator->_member = _entry; \ + } while (0) + +/** + * Insert the element at the next position in the list. This macro may be + * used to insert a list into a list. + * + * struct foo *elem = malloc(...); + * nt_list_init(elem, next) + * nt_list_insert(elem, list, struct foo, next); + * + * Resulting list order: + * list_item_0 -> elem_item_0 -> elem_item_1 ... -> list_item_1 -> ... + * + * @param entry An entry (or list) to append to the list + * @param list The list to insert to. This list must be a valid list, not + * NULL. + * @param type The list type + * @param member Member name of the field pointing to next struct + */ +#define nt_list_insert(_entry, _list, _type, _member) \ + do { \ + nt_list_append((_list)->_member, _entry, _type, _member); \ + (_list)->_member = _entry; \ + } while (0) + +/** + * Delete the entry from the list by iterating through the list and + * removing any reference from the list to the entry. + * + * Example: + * struct foo *elem = + * nt_list_del(elem, list, struct foo, next); + * + * @param entry The entry to delete from the list. entry is always + * re-initialized as a null-terminated list. + * @param list The list containing the entry, set to the new list without + * the removed entry. + * @param type The list type + * @param member Member name of the field pointing to the next entry + */ +#define nt_list_del(_entry, _list, _type, _member) \ + do { \ + _type *__e = _entry; \ + if (__e == NULL || _list == NULL) break; \ + if ((_list) == __e) { \ + _list = __e->_member; \ + } else { \ + _type *__prev = _list; \ + while (__prev->_member && __prev->_member != __e) \ + __prev = nt_list_next(__prev, _member); \ + if (__prev->_member) \ + __prev->_member = __e->_member; \ + } \ + nt_list_init(__e, _member); \ + } while(0) + +/** + * DO NOT USE THIS. + * This is a remainder of the xfree86 DDX attempt of having a set of generic + * list functions. Unfortunately, the xf86OptionRec uses it and we can't + * easily get rid of it. Do not use for new code. + */ +typedef struct generic_list_rec { + void *next; +} GenericListRec, *GenericListPtr, *glp; + +#endif From cce08f35fd006db81286493419124631eb9fb09f Mon Sep 17 00:00:00 2001 From: Volodymyr Samokhatko Date: Thu, 16 Mar 2023 18:34:33 +0100 Subject: [PATCH 3/6] libvncserver: timers --- CMakeLists.txt | 2 + include/rfb/rfbtimers.h | 125 +++++++++++++++ src/libvncserver/rfbtimers.c | 292 +++++++++++++++++++++++++++++++++++ 3 files changed, 419 insertions(+) create mode 100644 include/rfb/rfbtimers.h create mode 100644 src/libvncserver/rfbtimers.c diff --git a/CMakeLists.txt b/CMakeLists.txt index e3fff0037..eb74caf0c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -388,6 +388,7 @@ set(LIBVNCSERVER_SOURCES ${LIBVNCSERVER_DIR}/ultra.c ${LIBVNCSERVER_DIR}/scale.c ${CRYPTO_SOURCES} + ${LIBVNCSERVER_DIR}/rfbtimers.c ) set(LIBVNCCLIENT_SOURCES @@ -786,6 +787,7 @@ if(LIBVNCSERVER_INSTALL) include/rfb/rfblist.h include/rfb/rfbproto.h include/rfb/rfbregion.h + include/rfb/rfbtimers.h ) set_property(TARGET vncclient PROPERTY PUBLIC_HEADER ${INSTALL_HEADER_FILES}) diff --git a/include/rfb/rfbtimers.h b/include/rfb/rfbtimers.h new file mode 100644 index 000000000..1c6b06656 --- /dev/null +++ b/include/rfb/rfbtimers.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2023 AnatoScope SA. All Rights Reserved. + * + * This 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 software 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 software; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Copyright 1987, 1998 The Open Group + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name of The Open Group shall not be + * used in advertising or otherwise to promote the sale, use or other dealings + * in this Software without prior written authorization from The Open Group. + * + * Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts. + * + * All Rights Reserved + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, + * provided that the above copyright notice appear in all copies and that + * both that copyright notice and this permission notice appear in + * supporting documentation, and that the name of Digital not be + * used in advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING + * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL + * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR + * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* + * Originally derived from TurboVNC ff35d99e9aebb3905c2d90bea7c3305b63c853cd + */ + +#ifndef _RFB_TIMERS_H_ +#define _RFB_TIMERS_H_ + +typedef struct _rfbTimersRec rfbTimers; +typedef rfbTimers* rfbTimersPtr; + +typedef struct _rfbTimerRec rfbTimer; +typedef rfbTimer* rfbTimerPtr; + +/** + * @param timer timer that invoked the callback + * @param time current time + * @param arg user data to pass + * @return if non-zero then set the timer to fire again after that time + */ +typedef unsigned int (*rfbTimerCallback) (rfbTimerPtr timer, unsigned int time, void *arg); + +/** + * Create a list for timers. + */ +rfbTimersPtr rfbTimersCreate(); + +/** + * Destroy a list of timers. + */ +void rfbTimersDestroy(rfbTimersPtr timers); + +/** + * Invoke callbacks that were scheduled to run after specified time. + */ +void rfbTimerCheck(rfbTimersPtr timers); + +/** + * Schedule a callback to run after specified time, create the timer if needed. + * + * @param timers timer list + * @param timer timer to use (NULL creates a new one) + * @param millis time in milliseconds after which the callback is allowed to be invoked + * @param arg user data to associate with the timer + * @return pointer to a timer that has to be freed by calling rfbTimerFree() (no need to recreate the timer for every rfbTimerSet()) + */ +rfbTimerPtr rfbTimerSet(rfbTimersPtr timers, rfbTimerPtr timer, unsigned int millis, rfbTimerCallback func, void *arg); + +/** + * Stop timer. + * + * @param timers timer list + * @param timer timer to stop + */ +void rfbTimerCancel(rfbTimersPtr timers, rfbTimerPtr timer); + +/** + * Cancel and delete a timer. + * + * @param timers timer list + * @param timer timer to delete + */ +void rfbTimerFree(rfbTimersPtr timers, rfbTimerPtr timer); + +#endif /* _RFB_TIMERS_H_ */ diff --git a/src/libvncserver/rfbtimers.c b/src/libvncserver/rfbtimers.c new file mode 100644 index 000000000..2a9e85e30 --- /dev/null +++ b/src/libvncserver/rfbtimers.c @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2023 AnatoScope SA. All Rights Reserved. + * + * This 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 software 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 software; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + * + * Copyright 1987, 1998 The Open Group + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name of The Open Group shall not be + * used in advertising or otherwise to promote the sale, use or other dealings + * in this Software without prior written authorization from The Open Group. + * + * Copyright 1987 by Digital Equipment Corporation, Maynard, Massachusetts. + * + * All Rights Reserved + * + * Permission to use, copy, modify, and distribute this software and its + * documentation for any purpose and without fee is hereby granted, + * provided that the above copyright notice appear in all copies and that + * both that copyright notice and this permission notice appear in + * supporting documentation, and that the name of Digital not be + * used in advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING + * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL + * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR + * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* + * Originally derived from TurboVNC ff35d99e9aebb3905c2d90bea7c3305b63c853cd + */ + +#include + +#ifdef LIBVNCSERVER_HAVE_SYS_TIME_H +#include +#endif + +#include +#include + +#include +#include +#include + +struct _rfbTimerRec { + struct rfb_list list; + unsigned int expires; + unsigned int delta; + rfbTimerCallback callback; + void *arg; +}; + +struct _rfbTimersRec { + MUTEX(listmutex); + struct rfb_list timers; +}; + +static inline rfbTimerPtr first_timer(rfbTimersPtr timers_ctx) +{ + /* inline rfb_list_is_empty which can't handle volatile */ + if (timers_ctx->timers.next == &timers_ctx->timers) + return NULL; + return rfb_list_first_entry(&timers_ctx->timers, struct _rfbTimerRec, list); +} + +#if !defined LIBVNCSERVER_HAVE_GETTIMEOFDAY && defined WIN32 +#include +#include +#include + +static void gettimeofday(struct timeval* tv,char* dummy) +{ + SYSTEMTIME t; + GetSystemTime(&t); + tv->tv_sec=t.wHour*3600+t.wMinute*60+t.wSecond; + tv->tv_usec=t.wMilliseconds*1000; +} +#endif + +#if (defined WIN32 && defined __MINGW32__) || defined(__CYGWIN__) +static unsigned int GetTimeInMillis(void) +{ + return GetTickCount(); +} +#else +static unsigned int GetTimeInMillis(void) +{ + struct timeval tv; + +#ifdef MONOTONIC_CLOCK + struct timespec tp; + + if (!clockid) { +#ifdef CLOCK_MONOTONIC_COARSE + if (clock_getres(CLOCK_MONOTONIC_COARSE, &tp) == 0 && + (tp.tv_nsec / 1000) <= 1000 && + clock_gettime(CLOCK_MONOTONIC_COARSE, &tp) == 0) + clockid = CLOCK_MONOTONIC_COARSE; + else +#endif + if (clock_gettime(CLOCK_MONOTONIC, &tp) == 0) + clockid = CLOCK_MONOTONIC; + else + clockid = ~0L; + } + if (clockid != ~0L && clock_gettime(clockid, &tp) == 0) + return (tp.tv_sec * 1000) + (tp.tv_nsec / 1000000L); +#endif + + gettimeofday(&tv, NULL); + return (tv.tv_sec * 1000) + (tv.tv_usec / 1000); +} +#endif + +static void timers_lock(rfbTimersPtr timers_ctx) +{ + LOCK(timers_ctx->listmutex); +} + +static void timers_unlock(rfbTimersPtr timers_ctx) +{ + UNLOCK(timers_ctx->listmutex); +} + +static inline int timer_pending(rfbTimerPtr timer) { + return !rfb_list_is_empty(&timer->list); +} + +static void DoTimer(rfbTimersPtr timers_ctx, rfbTimerPtr timer, unsigned int now) +{ + unsigned int newTime; + + rfb_list_del(&timer->list); + newTime = (*timer->callback)(timer, now, timer->arg); + if (newTime) + rfbTimerSet(timers_ctx, timer, newTime, timer->callback, timer->arg); +} + +static void DoTimers(rfbTimersPtr timers_ctx, unsigned int now) +{ + rfbTimerPtr timer; + + timers_lock(timers_ctx); + while ((timer = first_timer(timers_ctx))) { + if ((int) (timer->expires - now) > 0) + break; + rfb_list_del(&timer->list); + timers_unlock(timers_ctx); + unsigned int newTime = (*timer->callback)(timer, now, timer->arg); + if (newTime) + rfbTimerSet(timers_ctx, timer, newTime, timer->callback, timer->arg); + timers_lock(timers_ctx); + } + timers_unlock(timers_ctx); +} + +rfbTimersPtr rfbTimersCreate() +{ + rfbTimersPtr timers_ctx = (rfbTimersPtr) malloc(sizeof(rfbTimers)); + if (!timers_ctx) { + return NULL; + } + + rfbTimerPtr timer, tmp; + + INIT_MUTEX_RECURSIVE(timers_ctx->listmutex); + rfb_list_init((struct rfb_list*) &timers_ctx->timers); + + rfb_list_for_each_entry_safe(timer, tmp, &timers_ctx->timers, list) + { + rfb_list_del(&timer->list); + free(timer); + } + + return timers_ctx; +} + +void rfbTimersDestroy(rfbTimersPtr timers_ctx) +{ + rfbTimerPtr timer, tmp; + + if (!timers_ctx) { + return; + } + + TINI_MUTEX(timers_ctx->listmutex); + + rfb_list_for_each_entry_safe(timer, tmp, &timers_ctx->timers, list) + { + rfb_list_del(&timer->list); + free(timer); + } + + free(timers_ctx); +} + +void rfbTimerCheck(rfbTimersPtr timers_ctx) +{ + DoTimers(timers_ctx, GetTimeInMillis()); +} + +rfbTimerPtr rfbTimerSet(rfbTimersPtr timers_ctx, rfbTimerPtr timer, unsigned int millis, rfbTimerCallback func, void *arg) +{ + rfbTimerPtr existing, tmp; + unsigned int now = GetTimeInMillis(); + + if (!timer) { + timer = calloc(1, sizeof(struct _rfbTimerRec)); + if (!timer) + return NULL; + rfb_list_init(&timer->list); + } else { + timers_lock(timers_ctx); + if (timer_pending(timer)) { + rfb_list_del(&timer->list); + } + timers_unlock(timers_ctx); + } + if (!millis) + return timer; + timer->delta = millis; + millis += now; + timer->expires = millis; + timer->callback = func; + timer->arg = arg; + timers_lock(timers_ctx); + + /* Sort into list */ + rfb_list_for_each_entry_safe(existing, tmp, &timers_ctx->timers, list) + if ((int) (existing->expires - millis) > 0) + break; + /* This even works at the end of the list -- existing->list will be timers */ + rfb_list_add(&timer->list, existing->list.prev); + + /* Check to see if the timer is ready to run now */ + if ((int) (millis - now) <= 0) + DoTimer(timers_ctx, timer, now); + + timers_unlock(timers_ctx); + return timer; +} + +void rfbTimerCancel(rfbTimersPtr timers_ctx, rfbTimerPtr timer) +{ + if (!timer) + return; + timers_lock(timers_ctx); + rfb_list_del(&timer->list); + timers_unlock(timers_ctx); +} + +void rfbTimerFree(rfbTimersPtr timers_ctx, rfbTimerPtr timer) +{ + if (!timer) + return; + rfbTimerCancel(timers_ctx, timer); + free(timer); +} From 0e2a0d269f02fe3d3a21b17efd3cce1cd80f2975 Mon Sep 17 00:00:00 2001 From: Volodymyr Samokhatko Date: Wed, 15 Feb 2023 15:04:25 +0100 Subject: [PATCH 4/6] libvncserver/sockets: cork --- src/libvncserver/sockets.c | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/libvncserver/sockets.c b/src/libvncserver/sockets.c index 1bf6cbf15..62a18e75b 100644 --- a/src/libvncserver/sockets.c +++ b/src/libvncserver/sockets.c @@ -20,6 +20,7 @@ */ /* + * Copyright (C) 2012-2020, 2022-2023 D. R. Commander * Copyright (C) 2011-2012 Christian Beier * Copyright (C) 2005 Rohit Kumar, Johannes E. Schindelin * OSXvnc Copyright (C) 2001 Dan McGuirk . @@ -333,6 +334,52 @@ void rfbShutdownSockets(rfbScreenInfoPtr rfbScreen) #endif } +/* + * rfbCorkSock enables the TCP cork functionality on Linux to inform the TCP + * layer to send only complete packets + */ + +void rfbCorkSock(int sock) +{ + static int alreadywarned = 0; +#ifdef TCP_CORK + int one = 1; + + if (setsockopt(sock, IPPROTO_TCP, TCP_CORK, (char *)&one, sizeof(one)) < 0) { + if (!alreadywarned) { + rfbLogPerror("Could not enable TCP corking"); + alreadywarned = 1; + } + } +#else + if (!alreadywarned) { + rfbLogPerror("TCP corking not available on this system."); + alreadywarned = 1; + } +#endif +} + + +/* + * rfbUncorkSock disables corking and sends all partially-complete packets + */ + +void rfbUncorkSock(int sock) +{ +#ifdef TCP_CORK + static int alreadywarned = 0; + int zero = 0; + + if (setsockopt(sock, IPPROTO_TCP, TCP_CORK, (char *)&zero, + sizeof(zero)) < 0) { + if (!alreadywarned) { + rfbLogPerror("Could not disable TCP corking"); + alreadywarned = 1; + } + } +#endif +} + /* * rfbCheckFds is called from ProcessInputEvents to check for input on the RFB * socket(s). If there is input to process, the appropriate function in the From c06e76c77956d510af6328ed209a1a85b99f2c42 Mon Sep 17 00:00:00 2001 From: Volodymyr Samokhatko Date: Wed, 15 Feb 2023 15:10:53 +0100 Subject: [PATCH 5/6] libvncserver/sockets: skip --- src/libvncserver/sockets.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/libvncserver/sockets.c b/src/libvncserver/sockets.c index 62a18e75b..edc6712b0 100644 --- a/src/libvncserver/sockets.c +++ b/src/libvncserver/sockets.c @@ -794,6 +794,34 @@ int rfbReadExact(rfbClientPtr cl,char* buf,int len) return(rfbReadExactTimeout(cl,buf,len,rfbMaxClientWait)); } +inline static unsigned min(unsigned a, unsigned b) +{ + return a > b ? b : a; +} + +/* + * SkipExact reads an exact number of bytes on a TCP socket into a temporary + * buffer and then discards them. Returns 1 on success, 0 if the other end has + * closed, or -1 if an error occurred (errno is set to ETIMEDOUT if it timed + * out). + */ + +int rfbSkipExact(rfbClientPtr cl, int len) +{ + char *tmpbuf = NULL; + int bufLen = min(len, 65536), i, retval = 1; + + tmpbuf = (char *)malloc(bufLen); + + for (i = 0; i < len; i += bufLen) { + retval = rfbReadExact(cl, tmpbuf, min(bufLen, len - i)); + if (retval <= 0) break; + } + + free(tmpbuf); + return retval; +} + /* * PeekExact peeks at an exact number of bytes from a client. Returns 1 if * those bytes have been read, 0 if the other end has closed, or -1 if an From 8941e0259bc83cb9937e512bfbbc24088fbd82c5 Mon Sep 17 00:00:00 2001 From: Volodymyr Samokhatko Date: Mon, 13 Feb 2023 09:01:11 +0100 Subject: [PATCH 6/6] libvncserver: continuous updates --- CMakeLists.txt | 1 + include/rfb/rfb.h | 42 ++ include/rfb/rfbproto.h | 60 +++ include/rfb/threading.h | 1 + src/libvncserver/cargs.c | 4 + src/libvncserver/flowcontrol.c | 724 +++++++++++++++++++++++++++++++++ src/libvncserver/flowcontrol.h | 84 ++++ src/libvncserver/main.c | 17 +- src/libvncserver/rfbserver.c | 206 +++++++++- src/libvncserver/sockets.c | 8 +- 10 files changed, 1135 insertions(+), 12 deletions(-) create mode 100644 src/libvncserver/flowcontrol.c create mode 100644 src/libvncserver/flowcontrol.h diff --git a/CMakeLists.txt b/CMakeLists.txt index eb74caf0c..4a890c778 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -388,6 +388,7 @@ set(LIBVNCSERVER_SOURCES ${LIBVNCSERVER_DIR}/ultra.c ${LIBVNCSERVER_DIR}/scale.c ${CRYPTO_SOURCES} + ${LIBVNCSERVER_DIR}/flowcontrol.c ${LIBVNCSERVER_DIR}/rfbtimers.c ) diff --git a/include/rfb/rfb.h b/include/rfb/rfb.h index 79a446f1b..2e6bb713b 100644 --- a/include/rfb/rfb.h +++ b/include/rfb/rfb.h @@ -41,7 +41,9 @@ extern "C" #include #include #include +#include #include +#include #if defined(ANDROID) || defined(LIBVNCSERVER_HAVE_ANDROID) #include @@ -372,9 +374,18 @@ typedef struct _rfbScreenInfo #ifdef LIBVNCSERVER_HAVE_LIBZ rfbSetXCutTextUTF8ProcPtr setXCutTextUTF8; #endif + rfbBool rfbCongestionControl; } rfbScreenInfo, *rfbScreenInfoPtr; +typedef struct { + struct timeval tv; + unsigned pos, extra; + char congested; + struct rfb_list entry; +} rfbRTTInfo; + + /** * rfbTranslateFnType is the type of translation functions. */ @@ -707,6 +718,34 @@ typedef struct _rfbClientRec { int tightPngDstDataLen; #endif #endif + + /* flow control extensions */ + + rfbBool enableCU; /**< client supports Continuous Updates */ + rfbBool enableFence; /**< client supports fence extension */ + + rfbBool continuousUpdates; + sraRegionPtr cuRegion; + + rfbTimersPtr timers; + + rfbBool pendingSyncFence, syncFence; + uint32_t fenceFlags; + unsigned fenceDataLen; + char fenceData[64]; + + unsigned lastPosition, extraBuffer; + struct timeval lastUpdate, lastSent; + unsigned baseRTT, congWindow; + rfbBool inSlowStart; + int sockOffset; + struct rfb_list pings; + rfbTimerPtr congestionTimer; + rfbRTTInfo lastPong; + struct timeval lastPongArrival; + int measurements; + struct timeval lastAdjustment; + unsigned minRTT, minCongestedRTT; } rfbClientRec, *rfbClientPtr; /** @@ -755,9 +794,12 @@ extern int rfbMaxClientWait; extern void rfbInitSockets(rfbScreenInfoPtr rfbScreen); extern void rfbShutdownSockets(rfbScreenInfoPtr rfbScreen); +extern void rfbCorkSock(int sock); +extern void rfbUncorkSock(int sock); extern void rfbDisconnectUDPSock(rfbScreenInfoPtr rfbScreen); extern void rfbCloseClient(rfbClientPtr cl); extern int rfbReadExact(rfbClientPtr cl, char *buf, int len); +extern int rfbSkipExact(rfbClientPtr cl, int len); extern int rfbReadExactTimeout(rfbClientPtr cl, char *buf, int len,int timeout); extern int rfbPeekExactTimeout(rfbClientPtr cl, char *buf, int len,int timeout); extern int rfbWriteExact(rfbClientPtr cl, const char *buf, int len); diff --git a/include/rfb/rfbproto.h b/include/rfb/rfbproto.h index ebcd303cc..409afd630 100644 --- a/include/rfb/rfbproto.h +++ b/include/rfb/rfbproto.h @@ -431,6 +431,8 @@ typedef struct { /* Modif cs@2005 */ /* PalmVNC 1.4 & 2.0 SetScale Factor message */ #define rfbPalmVNCSetScaleFactor 0xF +#define rfbEnableContinuousUpdates 150 +#define rfbEndOfContinuousUpdates 150 /* Xvp message - bidirectional */ #define rfbXvp 250 /* SetDesktopSize client -> server message */ @@ -438,6 +440,11 @@ typedef struct { #define rfbQemuEvent 255 +/*----------------------------------------------------------------------------- + * server -> client and client -> server + */ + +#define rfbFence 248 /***************************************************************************** @@ -528,6 +535,9 @@ typedef struct { #define rfbEncodingQualityLevel8 0xFFFFFFE8 #define rfbEncodingQualityLevel9 0xFFFFFFE9 +#define rfbEncodingContinuousUpdates 0xFFFFFEC7 +#define rfbEncodingFence 0xFFFFFEC8 + #define rfbEncodingQemuExtendedKeyEvent 0xFFFFFEFE /* -258 */ #define rfbEncodingExtendedClipboard 0xC0A1E5CE @@ -538,6 +548,37 @@ typedef struct { #define rfbEncodingServerIdentity 0xFFFE0003 +/***************************************************************************** + * + * Message definitions (server -> client and client -> server) + * + *****************************************************************************/ + +/*----------------------------------------------------------------------------- + * Fence + */ + +/* flags */ +#define rfbFenceFlagBlockBefore 1 +#define rfbFenceFlagBlockAfter 2 +#define rfbFenceFlagSyncNext 4 +#define rfbFenceFlagRequest 0x80000000 +#define rfbFenceFlagsSupported (rfbFenceFlagBlockBefore | \ + rfbFenceFlagBlockAfter | \ + rfbFenceFlagSyncNext | \ + rfbFenceFlagRequest) + +typedef struct _rfbFenceMsg { + uint8_t type; /* always rfbFence */ + uint8_t pad[3]; + uint32_t flags; + uint8_t length; + /* Followed by char data[length] */ +} rfbFenceMsg; + +#define sz_rfbFenceMsg 9 + + /***************************************************************************** * * Server -> client message definitions @@ -1267,6 +1308,7 @@ typedef union { rfbTextChatMsg tc; rfbXvpMsg xvp; rfbExtDesktopSizeMsg eds; + rfbFenceMsg f; } rfbServerToClientMsg; @@ -1523,6 +1565,22 @@ typedef struct _rfbSetSWMsg { #define sz_rfbSetSWMsg 6 +/*----------------------------------------------------------------------------- + * EnableContinuousUpdates + */ + +typedef struct _rfbEnableContinuousUpdatesMsg { + uint8_t type; /* always rfbEnableContinuousUpdates */ + uint8_t enable; + uint16_t x; + uint16_t y; + uint16_t w; + uint16_t h; +} rfbEnableContinuousUpdatesMsg; + +#define sz_rfbEnableContinuousUpdatesMsg 10 + + /*----------------------------------------------------------------------------- * Union of all client->server messages. @@ -1545,6 +1603,8 @@ typedef union { rfbTextChatMsg tc; rfbXvpMsg xvp; rfbSetDesktopSizeMsg sdm; + rfbEnableContinuousUpdatesMsg ecu; + rfbFenceMsg f; } rfbClientToServerMsg; /* diff --git a/include/rfb/threading.h b/include/rfb/threading.h index fefb7cc13..d1c1b31d5 100644 --- a/include/rfb/threading.h +++ b/include/rfb/threading.h @@ -77,6 +77,7 @@ #endif #elif defined(LIBVNCSERVER_HAVE_WIN32THREADS) #include +#include #define LOCK(mutex) EnterCriticalSection(&(mutex)) #define UNLOCK(mutex) LeaveCriticalSection(&(mutex)) #define MUTEX(mutex) CRITICAL_SECTION (mutex) diff --git a/src/libvncserver/cargs.c b/src/libvncserver/cargs.c index 277438e1c..cf9c89a15 100644 --- a/src/libvncserver/cargs.c +++ b/src/libvncserver/cargs.c @@ -43,6 +43,8 @@ rfbUsage(void) "new non-shared\n" " connection comes in (refuse new connection " "instead)\n"); + fprintf(stderr, "-noflowcontrol when continuous updates are enabled, send updates\n" + " whether or not the viewer is ready to receive them\n"); #ifdef LIBVNCSERVER_WITH_WEBSOCKETS fprintf(stderr, "-sslkeyfile path set path to private key file for encrypted WebSockets connections\n"); fprintf(stderr, "-sslcertfile path set path to certificate file for encrypted WebSockets connections\n"); @@ -158,6 +160,8 @@ rfbProcessArguments(rfbScreenInfoPtr rfbScreen,int* argc, char *argv[]) rfbScreen->neverShared = TRUE; } else if (strcmp(argv[i], "-dontdisconnect") == 0) { rfbScreen->dontDisconnect = TRUE; + } else if (strcmp(argv[i], "-noflowcontrol") == 0) { + rfbScreen->rfbCongestionControl = FALSE; } else if (strcmp(argv[i], "-httpdir") == 0) { /* -httpdir directory-path */ if (i + 1 >= *argc) { rfbUsage(); diff --git a/src/libvncserver/flowcontrol.c b/src/libvncserver/flowcontrol.c new file mode 100644 index 000000000..08ed0fba9 --- /dev/null +++ b/src/libvncserver/flowcontrol.c @@ -0,0 +1,724 @@ +/* + * flowcontrol.c - implement RFB flow control extensions + */ + +/* + * Copyright (C) 2023 AnatoScope SA. All Rights Reserved. + * Copyright (C) 2012, 2014, 2017-2018, 2021, 2023 D. R. Commander. + * All Rights Reserved. + * Copyright (C) 2018 Peter Åstrand for Cendio AB. All Rights Reserved. + * Copyright (C) 2011, 2015 Pierre Ossman for Cendio AB. All Rights Reserved. + * + * This 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 software 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 software; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +/* + * This code implements congestion control in the same manner as TCP, in order + * to avoid excessive latency in the transport. This is needed because "buffer + * bloat" is unfortunately still a very real problem. + * + * The basic principle is that described in RFC 5681 (TCP Congestion Control), + * with the addition of using the TCP Vegas algorithm. The reason we use Vegas + * is that we run on top of a reliable transport, so we need a latency-based + * algorithm rather than a loss-based one. There is also a lot of + * interpolation in our algorithm, because our measurements have poor + * granularity. + * + * We use a simplistic form of slow start in order to ramp up quickly from an + * idle state. We do not have any persistent threshold, though, as there is + * too much noise for it to be reliable. + */ + +/* + * Originally derived from TurboVNC ff35d99e9aebb3905c2d90bea7c3305b63c853cd + */ + +#include + +#ifdef LIBVNCSERVER_HAVE_SYS_TIME_H +#include +#endif + +#include +#include +#include + +#include "flowcontrol.h" + +#if !defined LIBVNCSERVER_HAVE_GETTIMEOFDAY && defined WIN32 +#include +#include +#include + +static void gettimeofday(struct timeval* tv,char* dummy) +{ + SYSTEMTIME t; + GetSystemTime(&t); + tv->tv_sec=t.wHour*3600+t.wMinute*60+t.wSecond; + tv->tv_usec=t.wMilliseconds*1000; +} +#endif + +/* #define CONGESTION_DEBUG */ + + +/* This window should get us going fairly quickly on a network with decent + bandwidth. If it's too high, then it will rapidly be reduced and stay + low. */ +static const unsigned INITIAL_WINDOW = 16384; + +/* TCP's minimum window is 3 * MSS, but since we don't know the MSS, we + make a guess at 4 KB (it's probably a bit higher.) */ +static const unsigned MINIMUM_WINDOW = 4096; + +/* The current default maximum window for Linux (4 MB.) This should be a good + limit for now... */ +static const unsigned MAXIMUM_WINDOW = 4194304; + + +static rfbBool IsCongested(rfbClientPtr); +static int GetUncongestedETA(rfbClientPtr); +static unsigned GetExtraBuffer(rfbClientPtr); +static unsigned GetInFlight(rfbClientPtr); +static uint32_t congestionCallback(rfbTimerPtr, unsigned int, void*); +static void UpdateCongestion(rfbClientPtr); + + +#ifndef min +inline static unsigned min(unsigned a, unsigned b) +{ + return a > b ? b : a; +} +#endif + +#ifndef max +inline static unsigned max(unsigned a, unsigned b) +{ + return a > b ? a : b; +} +#endif + +static time_t msBetween(const struct timeval *first, + const struct timeval *second) +{ + time_t diff; + + diff = (second->tv_sec - first->tv_sec) * 1000; + + diff += second->tv_usec / 1000; + diff -= first->tv_usec / 1000; + + return diff; +} + +static time_t msSince(const struct timeval *then) +{ + struct timeval now; + + gettimeofday(&now, NULL); + + return msBetween(then, &now); +} + +static rfbBool isBefore(const struct timeval *first, const struct timeval *second) +{ + if (first->tv_sec < second->tv_sec) + return TRUE; + if (first->tv_sec > second->tv_sec) + return FALSE; + if (first->tv_usec < second->tv_usec) + return TRUE; + return FALSE; +} + +/* Compare two positions, even if integer wraparound has occurred. */ +static inline rfbBool isAfter(unsigned a, unsigned b) +{ + return a != b && a - b <= UINT_MAX / 2; +} + + +void rfbInitFlowControl(rfbClientPtr cl) +{ + cl->congWindow = INITIAL_WINDOW; + cl->inSlowStart = TRUE; + gettimeofday(&cl->lastUpdate, NULL); + gettimeofday(&cl->lastSent, NULL); + gettimeofday(&cl->lastPongArrival, NULL); + gettimeofday(&cl->lastAdjustment, NULL); +} + + +/* + * rfbUpdatePosition() registers the current stream position. It can and + * should be called often. + */ + +void rfbUpdatePosition(rfbClientPtr cl, unsigned pos) +{ + struct timeval now; + unsigned delta, consumed; + + gettimeofday(&now, NULL); + + delta = pos - cl->lastPosition; + if ((delta > 0) || (cl->extraBuffer > 0)) + cl->lastSent = now; + + /* Idle for too long? + We use a very crude RTO calculation in order to keep things simple. + + FIXME: Implement RFC 2861. */ + if (msBetween(&cl->lastSent, &now) > max(cl->baseRTT * 2, 100)) { + +#ifdef CONGESTION_DEBUG + rfbLog("Connection idle for %d ms. Resetting congestion control.\n", + msBetween(&cl->lastSent, &now)); +#endif + + /* Close congestion window and redo wire latency measurement. */ + cl->congWindow = min(INITIAL_WINDOW, cl->congWindow); + cl->baseRTT = (unsigned)-1; + cl->measurements = 0; + gettimeofday(&cl->lastAdjustment, NULL); + cl->minRTT = cl->minCongestedRTT = (unsigned)-1; + cl->inSlowStart = TRUE; + } + + /* Commonly we will be in a state of overbuffering. We need to estimate the + extra delay that this causes, so we can separate it from the delay caused + by an incorrect congestion window. (We cannot do this until we have a RTT + measurement, though.) */ + if (cl->baseRTT != (unsigned)-1) { + cl->extraBuffer += delta; + consumed = msBetween(&cl->lastUpdate, &now) * cl->congWindow / cl->baseRTT; + if (cl->extraBuffer < consumed) + cl->extraBuffer = 0; + else + cl->extraBuffer -= consumed; + } + + cl->lastPosition = pos; + cl->lastUpdate = now; +} + + +rfbBool rfbSendRTTPing(rfbClientPtr cl) +{ + rfbRTTInfo *rttInfo; + char type; + + if (!cl->enableFence) + return TRUE; + + rfbUpdatePosition(cl, cl->sockOffset); + + /* We need to make sure that any old updates are already processed by the + time we get the response back. This allows us to reliably throttle + back if the client or the network overloads. */ + type = 1; + if (!rfbSendFence(cl, rfbFenceFlagRequest | rfbFenceFlagBlockBefore, + sizeof(type), &type)) + return FALSE; + + rttInfo = (rfbRTTInfo*) calloc(sizeof(rfbRTTInfo), 1); + + gettimeofday(&rttInfo->tv, NULL); + rttInfo->pos = cl->lastPosition; + rttInfo->extra = GetExtraBuffer(cl); + rttInfo->congested = IsCongested(cl); + + rfb_list_append(&rttInfo->entry, &cl->pings); + + return TRUE; +} + + +static void HandleRTTPong(rfbClientPtr cl) +{ + struct timeval now; + rfbRTTInfo *rttInfo; + unsigned rtt, delay; + + if (rfb_list_is_empty(&cl->pings)) + return; + + gettimeofday(&now, NULL); + + rttInfo = rfb_list_first_entry(&cl->pings, rfbRTTInfo, entry); + rfb_list_del(&rttInfo->entry); + + cl->lastPong = *rttInfo; + cl->lastPongArrival = now; + + rtt = msBetween(&rttInfo->tv, &now); + if (rtt < 1) + rtt = 1; + + /* Try to estimate wire latency by tracking the lowest observed latency. */ + if (rtt < cl->baseRTT) + cl->baseRTT = rtt; + + /* Pings sent before the last adjustment aren't interesting, as they aren't a + measure of the current congestion window. */ + if (isBefore(&rttInfo->tv, &cl->lastAdjustment)) + return; + + /* Estimate added delay because of overtaxed buffers (see above.) */ + delay = rttInfo->extra * cl->baseRTT / cl->congWindow; + if (delay < rtt) + rtt -= delay; + else + rtt = 1; + + /* An observed latency less than the wire latency means that we've + understimated the congestion window. We can't really determine by how + much, though, so we pretend that we observed no buffer latency at all. */ + if (rtt < cl->baseRTT) + rtt = cl->baseRTT; + + /* Record the minimum observed delay (hopefully ignoring jitter), and let the + congestion control algorithm do its thing. + + NOTE: Our algorithm is delay-based rather than loss-based, which means + that we need to look at pongs even if they weren't limited by the current + window ("congested"). Otherwise we will fail to detect increasing + congestion until the application exceeds the congestion window. */ + if (rtt < cl->minRTT) + cl->minRTT = rtt; + if (rttInfo->congested) { + if (rtt < cl->minCongestedRTT) + cl->minCongestedRTT = rtt; + } + + cl->measurements++; + UpdateCongestion(cl); + + free(rttInfo); +} + + +static rfbBool IsCongested(rfbClientPtr cl) +{ + if (GetInFlight(cl) < cl->congWindow) + return FALSE; + + return TRUE; +} + + +/* + * rfbIsCongested() determines if the transport is currently congested or if + * more data can be sent. + */ + +rfbBool rfbIsCongested(rfbClientPtr cl) +{ + int eta; + + if (!cl->enableFence) + return FALSE; + + rfbTimerCancel(cl->timers, cl->congestionTimer); + + rfbUpdatePosition(cl, cl->sockOffset); + if (!IsCongested(cl)) + return FALSE; + + eta = GetUncongestedETA(cl); + cl->congestionTimer = rfbTimerSet(cl->timers, cl->congestionTimer, eta <= 0 ? 1 : eta, + congestionCallback, cl); + return TRUE; +} + + +/* + * GetUncongestedETA() estimates the number of milliseconds until the transport + * will no longer be congested. It returns 0 if there is no congestion and -1 + * if it is unknown when the transport will no longer be congested. + */ + +static int GetUncongestedETA(rfbClientPtr cl) +{ + unsigned targetAcked; + + const rfbRTTInfo *prevPing; + unsigned eta, elapsed; + unsigned etaNext, delay; + + rfbRTTInfo *iter; + + targetAcked = cl->lastPosition - cl->congWindow; + + /* Simple case? */ + if (isAfter(cl->lastPong.pos, targetAcked)) + return 0; + + /* No measurements yet? */ + if (cl->baseRTT == (unsigned)-1) + return -1; + + prevPing = &cl->lastPong; + eta = 0; + elapsed = msSince(&cl->lastPongArrival); + + /* Walk the ping queue and figure out which ping we are waiting for in order + to get to an uncongested state. */ + for (iter = NULL, iter = __container_of(cl->pings.next, iter, entry);; + iter = __container_of(iter->entry.next, iter, entry)) { + rfbRTTInfo curPing; + + /* If we aren't waiting for a pong that will clear the congested state, + then we have to estimate the final bit by pretending that we had a ping + just after the last position update. */ + if (&iter->entry == &cl->pings) { + curPing.tv = cl->lastUpdate; + curPing.pos = cl->lastPosition; + curPing.extra = cl->extraBuffer; + } else { + curPing = *iter; + } + + etaNext = msBetween(&prevPing->tv, &curPing.tv); + /* Compensate for buffering delays. */ + delay = curPing.extra * cl->baseRTT / cl->congWindow; + etaNext += delay; + delay = prevPing->extra * cl->baseRTT / cl->congWindow; + if (delay >= etaNext) + etaNext = 0; + else + etaNext -= delay; + + /* Found it? */ + if (isAfter(curPing.pos, targetAcked)) { + eta += etaNext * (curPing.pos - targetAcked) / + (curPing.pos - prevPing->pos); + if (elapsed > eta) + return 0; + else + return eta - elapsed; + } + + eta += etaNext; + prevPing = &*iter; + } + + return -1; +} + + +static unsigned GetExtraBuffer(rfbClientPtr cl) +{ + unsigned elapsed; + unsigned consumed; + + if (cl->baseRTT == (unsigned)-1) + return 0; + + elapsed = msSince(&cl->lastUpdate); + consumed = elapsed * cl->congWindow / cl->baseRTT; + + if (consumed >= cl->extraBuffer) + return 0; + else + return cl->extraBuffer - consumed; +} + + +static unsigned GetInFlight(rfbClientPtr cl) +{ + rfbRTTInfo nextPong; + unsigned etaNext, delay, elapsed, acked; + + /* Simple case? */ + if (cl->lastPosition == cl->lastPong.pos) + return 0; + + /* No measurements yet? */ + if (cl->baseRTT == (unsigned)-1) { + if (!rfb_list_is_empty(&cl->pings)) { + rfbRTTInfo *rttInfo = + rfb_list_first_entry(&cl->pings, rfbRTTInfo, entry); + return cl->lastPosition - rttInfo->pos; + } + return 0; + } + + /* If we aren't waiting for a pong, then we have to estimate things by + pretending that we had a ping just after the last position update. */ + if (rfb_list_is_empty(&cl->pings)) { + nextPong.tv = cl->lastUpdate; + nextPong.pos = cl->lastPosition; + nextPong.extra = cl->extraBuffer; + } else { + rfbRTTInfo *rttInfo = rfb_list_first_entry(&cl->pings, rfbRTTInfo, entry); + nextPong = *rttInfo; + } + + /* First, we need to estimate how many bytes have made it through completely. + To do this, we look at the next ping that should arrive, figure out how + far behind it should be, and interpolate the positions. */ + + etaNext = msBetween(&cl->lastPong.tv, &nextPong.tv); + /* Compensate for buffering delays. */ + delay = nextPong.extra * cl->baseRTT / cl->congWindow; + etaNext += delay; + delay = cl->lastPong.extra * cl->baseRTT / cl->congWindow; + if (delay >= etaNext) + etaNext = 0; + else + etaNext -= delay; + + elapsed = msSince(&cl->lastPongArrival); + + /* The pong should be here very soon. Be optimistic and assume we can + already use its value. */ + if (etaNext <= elapsed) + acked = nextPong.pos; + else { + acked = cl->lastPong.pos; + acked += (nextPong.pos - cl->lastPong.pos) * elapsed / etaNext; + } + + return cl->lastPosition - acked; +} + + +static uint32_t congestionCallback(rfbTimerPtr timer, uint32_t time, void *arg) +{ + rfbClientPtr cl = (rfbClientPtr)arg; + + LOCK(cl->updateMutex); + sraRegionPtr updateRegion = sraRgnCreateRgn(cl->modifiedRegion); + UNLOCK(cl->updateMutex); + LOCK(cl->sendMutex); + rfbSendFramebufferUpdate(cl, updateRegion); + UNLOCK(cl->sendMutex); + sraRgnDestroy(updateRegion); + + return 0; +} + + +static void UpdateCongestion(rfbClientPtr cl) +{ + unsigned diff; +#if defined(CONGESTION_DEBUG) && defined(TCP_INFO) + struct tcp_info tcp_info; + socklen_t tcp_info_length; +#endif + + /* In order to avoid noise, we want at least three measurements. */ + if (cl->measurements < 3) + return; + + /* The goal is to have a congestion window that is slightly too large, since + a "perfect" congestion window cannot be distinguished from one that is too + small. This translates to a goal of a few extra milliseconds of delay. */ + + diff = cl->minRTT - cl->baseRTT; + + if (diff > max(100, cl->baseRTT / 2)) { + /* We have no way of detecting loss, so assume that a massive latency spike + means packet loss. Adjust the window and go directly to congestion + avoidance. */ +#ifdef CONGESTION_DEBUG + rfbLog("Latency spike! Backing off...\n"); +#endif + cl->congWindow = cl->congWindow * cl->baseRTT / cl->minRTT; + cl->inSlowStart = FALSE; + } + + if (cl->inSlowStart) { + /* Slow start-- aggressive growth until we see congestion */ + + if (diff > 25) { + /* If we observe increased latency, then we assume we've hit the limit + and it's time to leave slow start and switch to congestion + avoidance. */ + cl->congWindow = cl->congWindow * cl->baseRTT / cl->minRTT; + cl->inSlowStart = FALSE; + } else { + /* It's not safe to increase the congestion window unless we actually + used all of it, so we look at minCongestedRTT and not minRTT. */ + + diff = cl->minCongestedRTT - cl->baseRTT; + if (diff < 25) + cl->congWindow *= 2; + } + } else { + /* Congestion avoidance (VEGAS) */ + + if (diff > 50) { + /* Slightly too fast */ + cl->congWindow -= 4096; + } else { + /* Only the "congested" pongs are checked to see if the window is too + small. */ + + diff = cl->minCongestedRTT - cl->baseRTT; + + if (diff < 5) { + /* Way too slow */ + cl->congWindow += 8192; + } else if (diff < 25) { + /* Too slow */ + cl->congWindow += 4096; + } + } + } + + if (cl->congWindow < MINIMUM_WINDOW) + cl->congWindow = MINIMUM_WINDOW; + if (cl->congWindow > MAXIMUM_WINDOW) + cl->congWindow = MAXIMUM_WINDOW; + +#ifdef CONGESTION_DEBUG + rfbLog("RTT: %d/%d ms (%d ms), Window: %d KB, Offset: %d KB, Bandwidth: %g Mbps%s\n", + cl->minRTT, cl->minCongestedRTT, cl->baseRTT, cl->congWindow / 1024, + cl->sockOffset / 1024, cl->congWindow * 8.0 / cl->baseRTT / 1000.0, + cl->inSlowStart ? " (slow start)" : ""); + +#ifdef TCP_INFO + tcp_info_length = sizeof(tcp_info); + if (getsockopt(cl->sock, SOL_TCP, TCP_INFO, (void *)&tcp_info, + &tcp_info_length) == 0) { + rfbLog("Socket: RTT: %d ms (+/- %d ms) Window %d KB\n", + tcp_info.tcpi_rtt / 1000, tcp_info.tcpi_rttvar / 1000, + tcp_info.tcpi_snd_mss * tcp_info.tcpi_snd_cwnd / 1024); + } +#endif + +#endif + + cl->measurements = 0; + gettimeofday(&cl->lastAdjustment, NULL); + cl->minRTT = cl->minCongestedRTT = (unsigned)-1; +} + + +/* + * rfbSendFence sends a fence message to a specific client + */ +rfbBool rfbSendFence(rfbClientPtr cl, uint32_t flags, unsigned len, + const char *data) +{ + rfbFenceMsg f; + + if (!cl->enableFence) { + rfbLog("ERROR in rfbSendFence: Client does not support fence extension\n"); + return FALSE; + } + if (len > 64) { + rfbLog("ERROR in rfbSendFence: Fence payload is too large\n"); + return FALSE; + } + if ((flags & ~rfbFenceFlagsSupported) != 0) { + rfbLog("ERROR in rfbSendFence: Unknown fence flags\n"); + return FALSE; + } + + memset(&f, 0, sz_rfbFenceMsg); + f.type = rfbFence; + f.flags = Swap32IfLE(flags); + f.length = len; + + if (rfbWriteExact(cl, (char *)&f, sz_rfbFenceMsg) < 0) { + rfbLogPerror("rfbSendFence: write"); + rfbCloseClient(cl); + return FALSE; + } + + if (len > 0) { + if (rfbWriteExact(cl, (char *)data, len) < 0) { + rfbLogPerror("rfbSendFence: write"); + rfbCloseClient(cl); + return FALSE; + } + } + return TRUE; +} + + +/* + * This is called whenever a client fence message is received. + */ +void rfbHandleFence(rfbClientPtr cl, uint32_t flags, unsigned len, const char *data) +{ + unsigned char type; + + if (flags & rfbFenceFlagRequest) { + + if (flags & rfbFenceFlagSyncNext) { + cl->pendingSyncFence = TRUE; + cl->fenceFlags = flags & (rfbFenceFlagBlockBefore | + rfbFenceFlagBlockAfter | + rfbFenceFlagSyncNext); + cl->fenceDataLen = len; + if (len > 0) + memcpy(cl->fenceData, data, len); + return; + } + + /* We handle everything synchronously, so we trivially honor these + modes */ + flags = flags & (rfbFenceFlagBlockBefore | rfbFenceFlagBlockAfter); + + rfbSendFence(cl, flags, len, data); + return; + } + + if (len < 1) + rfbLog("Fence of unusual size received\n"); + + type = data[0]; + + switch (type) { + case 0: + /* Initial dummy fence */ + break; + + case 1: + HandleRTTPong(cl); + break; + + default: + rfbLog("Fence of unusual size received\n"); + } +} + + +/* + * rfbSendEndOfCU sends an end of Continuous Updates message to a specific + * client + */ +rfbBool rfbSendEndOfCU(rfbClientPtr cl) +{ + uint8_t type = rfbEndOfContinuousUpdates; + + if (!cl->enableCU) { + rfbLog("ERROR in rfbSendEndOfCU: Client does not support Continuous Updates\n"); + return FALSE; + } + + if (rfbWriteExact(cl, (char *)&type, 1) < 0) { + rfbLogPerror("rfbSendEndOfCU: write"); + rfbCloseClient(cl); + return FALSE; + } + + return TRUE; +} diff --git a/src/libvncserver/flowcontrol.h b/src/libvncserver/flowcontrol.h new file mode 100644 index 000000000..a25b55b94 --- /dev/null +++ b/src/libvncserver/flowcontrol.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 AnatoScope SA. All Rights Reserved. + * Copyright (C) 2012, 2014, 2017-2018, 2021, 2023 D. R. Commander. + * All Rights Reserved. + * Copyright (C) 2018 Peter Åstrand for Cendio AB. All Rights Reserved. + * Copyright (C) 2011, 2015 Pierre Ossman for Cendio AB. All Rights Reserved. + * + * This 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 software 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 software; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +/* + * This code implements congestion control in the same manner as TCP, in order + * to avoid excessive latency in the transport. This is needed because "buffer + * bloat" is unfortunately still a very real problem. + * + * The basic principle is that described in RFC 5681 (TCP Congestion Control), + * with the addition of using the TCP Vegas algorithm. The reason we use Vegas + * is that we run on top of a reliable transport, so we need a latency-based + * algorithm rather than a loss-based one. There is also a lot of + * interpolation in our algorithm, because our measurements have poor + * granularity. + * + * We use a simplistic form of slow start in order to ramp up quickly from an + * idle state. We do not have any persistent threshold, though, as there is + * too much noise for it to be reliable. + */ + +/* + * Originally derived from TurboVNC ff35d99e9aebb3905c2d90bea7c3305b63c853cd + */ + +#ifndef LIBVNCSERVER_FLOWCONTROL_H_ +#define LIBVNCSERVER_FLOWCONTROL_H_ + +#include + +void rfbInitFlowControl(rfbClientPtr cl); + +/* + * rfbUpdatePosition() registers the current stream position. It can and + * should be called often. + */ + +void rfbUpdatePosition(rfbClientPtr cl, unsigned pos); + +rfbBool rfbSendRTTPing(rfbClientPtr cl); + +/* + * rfbIsCongested() determines if the transport is currently congested or if + * more data can be sent. + */ + +rfbBool rfbIsCongested(rfbClientPtr cl); + +/* + * Sends a fence message to a specific client. + */ +rfbBool rfbSendFence(rfbClientPtr cl, uint32_t flags, unsigned len, const char *data); + +/* + * This is to call for a received client fence message. + */ +void rfbHandleFence(rfbClientPtr cl, uint32_t flags, unsigned len, const char *data); + +/* + * rfbSendEndOfCU sends an end of Continuous Updates message to a specific + * client + */ +rfbBool rfbSendEndOfCU(rfbClientPtr cl); + +#endif /* LIBVNCSERVER_FLOWCONTROL_H_ */ diff --git a/src/libvncserver/main.c b/src/libvncserver/main.c index 32519f14a..db9c853ac 100644 --- a/src/libvncserver/main.c +++ b/src/libvncserver/main.c @@ -473,12 +473,15 @@ clientOutput(void *data) LOCK(cl->updateMutex); - if (sraRgnEmpty(cl->requestedRegion)) { - ; /* always require a FB Update Request (otherwise can crash.) */ + if (sraRgnEmpty(cl->requestedRegion) && !cl->continuousUpdates) { + ; /* require a FB Update Request unless continuous updates are enabled (otherwise can crash.) */ } else { haveUpdate = FB_UPDATE_PENDING(cl); if(!haveUpdate) { updateRegion = sraRgnCreateRgn(cl->modifiedRegion); + if (cl->continuousUpdates) { + sraRgnOr(cl->requestedRegion, cl->cuRegion); + } haveUpdate = sraRgnAnd(updateRegion,cl->requestedRegion); sraRgnDestroy(updateRegion); } @@ -491,9 +494,11 @@ clientOutput(void *data) UNLOCK(cl->updateMutex); } - /* OK, now, to save bandwidth, wait a little while for more - updates to come along. */ - THREAD_SLEEP_MS(cl->screen->deferUpdateTime); + if (!cl->continuousUpdates) { + /* OK, now, to save bandwidth, wait a little while for more + updates to come along. */ + THREAD_SLEEP_MS(cl->screen->deferUpdateTime); + } /* Now, get the region we're going to update, and remove it from cl->modifiedRegion _before_ we send the update. @@ -996,6 +1001,8 @@ rfbScreenInfoPtr rfbGetScreen(int* argc,char** argv, screen->permitFileTransfer = FALSE; + screen->rfbCongestionControl = TRUE; + if(!rfbProcessArguments(screen,argc,argv)) { free(screen); return NULL; diff --git a/src/libvncserver/rfbserver.c b/src/libvncserver/rfbserver.c index 3fed80e43..a721e22f2 100644 --- a/src/libvncserver/rfbserver.c +++ b/src/libvncserver/rfbserver.c @@ -38,6 +38,7 @@ #include #include "private.h" #include "rfb/rfbconfig.h" +#include "flowcontrol.h" #ifdef LIBVNCSERVER_HAVE_FCNTL_H #include @@ -504,6 +505,11 @@ rfbNewTCPOrUDPClient(rfbScreenInfoPtr rfbScreen, } } + cl->timers = rfbTimersCreate(); + rfb_list_init(&cl->pings); + cl->baseRTT = cl->minRTT = cl->minCongestedRTT = (unsigned)-1; + cl->cuRegion = sraRgnCreate(); + for(extension = rfbGetExtensionIterator(); extension; extension=extension->next) { void* data = NULL; @@ -555,6 +561,7 @@ rfbClientConnectionGone(rfbClientPtr cl) #if defined(LIBVNCSERVER_HAVE_LIBZ) && defined(LIBVNCSERVER_HAVE_LIBJPEG) int i; #endif + rfbRTTInfo *rttInfo, *tmp; LOCK(rfbClientListMutex); @@ -567,6 +574,8 @@ rfbClientConnectionGone(rfbClientPtr cl) UNLOCK(rfbClientListMutex); + rfbTimerFree(cl->timers, cl->congestionTimer); + #if defined(LIBVNCSERVER_HAVE_LIBPTHREAD) || defined(LIBVNCSERVER_HAVE_WIN32THREADS) if (cl->screen->backgroundLoop) { int i; @@ -652,6 +661,15 @@ rfbClientConnectionGone(rfbClientPtr cl) } #endif + sraRgnDestroy(cl->cuRegion); + + rfb_list_for_each_entry_safe(rttInfo, tmp, &cl->pings, entry) { + rfb_list_del(&rttInfo->entry); + free(rttInfo); + } + + rfbTimersDestroy(cl->timers); + rfbPrintStats(cl); rfbResetStats(cl); @@ -666,24 +684,44 @@ rfbClientConnectionGone(rfbClientPtr cl) void rfbProcessClientMessage(rfbClientPtr cl) { + rfbCorkSock(cl->sock); + + if (cl->pendingSyncFence) { + cl->syncFence = TRUE; + cl->pendingSyncFence = FALSE; + } + switch (cl->state) { case RFB_PROTOCOL_VERSION: rfbProcessClientProtocolVersion(cl); - return; + break; case RFB_SECURITY_TYPE: rfbProcessClientSecurityType(cl); - return; + break; case RFB_AUTHENTICATION: rfbAuthProcessClientMessage(cl); - return; + break; case RFB_INITIALISATION: case RFB_INITIALISATION_SHARED: + rfbInitFlowControl(cl); rfbProcessClientInitMessage(cl); - return; + break; default: + rfbTimerCheck(cl->timers); rfbProcessClientNormalMessage(cl); - return; + break; } + + if (cl->syncFence) { + LOCK(cl->sendMutex); + rfbBool sent = rfbSendFence(cl, cl->fenceFlags, cl->fenceDataLen, cl->fenceData); + UNLOCK(cl->sendMutex); + if (!sent) + return; + cl->syncFence = FALSE; + } + + rfbUncorkSock(cl->sock); } @@ -994,6 +1032,8 @@ rfbSendSupportedMessages(rfbClientPtr cl) /*rfbSetBit(msgs.client2server, rfbSetSW); */ /*rfbSetBit(msgs.client2server, rfbTextChat); */ rfbSetBit(msgs.client2server, rfbPalmVNCSetScaleFactor); + rfbSetBit(msgs.client2server, rfbEnableContinuousUpdates); + rfbSetBit(msgs.client2server, rfbFence); rfbSetBit(msgs.server2client, rfbFramebufferUpdate); rfbSetBit(msgs.server2client, rfbSetColourMapEntries); @@ -1002,6 +1042,8 @@ rfbSendSupportedMessages(rfbClientPtr cl) rfbSetBit(msgs.server2client, rfbResizeFrameBuffer); rfbSetBit(msgs.server2client, rfbPalmVNCReSizeFrameBuffer); rfbSetBit(msgs.client2server, rfbSetDesktopSize); + rfbSetBit(msgs.server2client, rfbEndOfContinuousUpdates); + rfbSetBit(msgs.server2client, rfbFence); if (cl->screen->xvpHook) { rfbSetBit(msgs.client2server, rfbXvp); @@ -2287,6 +2329,8 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) */ case rfbSetEncodings: { + rfbBool firstFence = !cl->enableFence; + rfbBool firstCU = !cl->enableCU; if ((n = rfbReadExact(cl, ((char *)&msg) + 1, sz_rfbSetEncodingsMsg - 1)) <= 0) { @@ -2405,6 +2449,20 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) cl->enableLastRectEncoding = TRUE; } break; + case rfbEncodingFence: + if (!cl->enableFence) { + rfbLog("Enabling Fence protocol extension for client %s\n", + cl->host); + cl->enableFence = TRUE; + } + break; + case rfbEncodingContinuousUpdates: + if (!cl->enableCU) { + rfbLog("Enabling Continuous Updates protocol extension for client %s\n", + cl->host); + cl->enableCU = TRUE; + } + break; case rfbEncodingNewFBSize: if (!cl->useNewFBSize) { rfbLog("Enabling NewFBSize protocol extension for client " @@ -2582,6 +2640,23 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) cl->enableCursorPosUpdates = FALSE; } + if (cl->enableFence && firstFence) { + char type = 0; + LOCK(cl->sendMutex); + rfbBool sent = rfbSendFence(cl, rfbFenceFlagRequest, sizeof(type), &type); + UNLOCK(cl->sendMutex); + if (!sent) + return; + } + + if (cl->enableCU && cl->enableFence && firstCU) { + LOCK(cl->sendMutex); + rfbBool sent = rfbSendEndOfCU(cl); + UNLOCK(cl->sendMutex); + if (!sent) + return; + } + return; } @@ -2936,6 +3011,91 @@ rfbProcessClientNormalMessage(rfbClientPtr cl) return; + case rfbEnableContinuousUpdates: + { + if ((n = rfbReadExact(cl, ((char *)&msg) + 1, + sz_rfbEnableContinuousUpdatesMsg - 1)) <= 0) { + if (n != 0) + rfbLogPerror("rfbProcessClientNormalMessage: read"); + rfbCloseClient(cl); + return; + } + + if (!cl->enableFence || !cl->enableCU) { + rfbLog("Ignoring request to enable continuous updates because the client does not\n"); + rfbLog("support the flow control extensions.\n"); + return; + } + + int x1 = Swap16IfLE(msg.ecu.x); + int y1 = Swap16IfLE(msg.ecu.y); + int x2 = x1 + Swap16IfLE(msg.ecu.w); + int y2 = y1 + Swap16IfLE(msg.ecu.h); + cl->cuRegion = sraRgnCreateRect(x1, y1, x2, y2); + + cl->continuousUpdates = msg.ecu.enable; + if (cl->continuousUpdates) { + LOCK(cl->updateMutex); + sraRgnMakeEmpty(cl->requestedRegion); + sraRegionPtr updateRegion = sraRgnCreateRgn(cl->modifiedRegion); + UNLOCK(cl->updateMutex); + LOCK(cl->sendMutex); + rfbBool sent = rfbSendFramebufferUpdate(cl, updateRegion); + UNLOCK(cl->sendMutex); + sraRgnDestroy(updateRegion); + if (!sent) + return; + } else { + LOCK(cl->sendMutex); + rfbBool sent = rfbSendEndOfCU(cl); + UNLOCK(cl->sendMutex); + if (!sent) + return; + } + + rfbLog("Continuous updates %s\n", + cl->continuousUpdates ? "enabled" : "disabled"); + return; + } + + case rfbFence: + { + uint32_t flags; + char data[64]; + + if ((n = rfbReadExact(cl, ((char *)&msg) + 1, + sz_rfbFenceMsg - 1)) <= 0) { + if (n != 0) + rfbLogPerror("rfbProcessClientNormalMessage: read"); + rfbCloseClient(cl); + return; + } + + flags = Swap32IfLE(msg.f.flags); + + if (msg.f.length > sizeof(data)) { + rfbLog("Ignoring fence. Payload of %d bytes is too large.\n", msg.f.length); + if ((n = rfbSkipExact(cl, msg.f.length)) <= 0) { + if (n != 0) + rfbLogPerror("rfbProcessClientNormalMessage: skip"); + rfbCloseClient(cl); + return; + } + } else { + if ((n = rfbReadExact(cl, (char *)&data, msg.f.length)) <= 0) { + if (n != 0) + rfbLogPerror("rfbProcessClientNormalMessage: read"); + rfbCloseClient(cl); + return; + } + LOCK(cl->sendMutex); + rfbHandleFence(cl, flags, msg.f.length, data); + UNLOCK(cl->sendMutex); + } + + return; + } + case rfbPalmVNCSetScaleFactor: cl->PalmVNC = TRUE; if ((n = rfbReadExact(cl, ((char *)&msg) + 1, @@ -3122,6 +3282,29 @@ rfbSendFramebufferUpdate(rfbClientPtr cl, rfbBool sendServerIdentity = FALSE; rfbBool result = TRUE; + rfbUpdatePosition(cl, cl->sockOffset); + + /* + * We're in the middle of processing a command that's supposed to be + * synchronised. Allowing an update to slip out right now might violate + * that synchronisation. + */ + + if (cl->syncFence) return TRUE; + + if (cl->state != RFB_NORMAL) return TRUE; + + /* Check that we actually have some space on the link and retry in a + bit if things are congested. */ + + if (cl->screen->rfbCongestionControl && rfbIsCongested(cl)) + return TRUE; + + /* In continuous mode, we will be outputting at least three distinct + messages. We need to aggregate these in order to not clog up TCP's + congestion window. */ + + rfbCorkSock(cl->sock); if(cl->screen->displayHook) cl->screen->displayHook(cl); @@ -3240,6 +3423,9 @@ rfbSendFramebufferUpdate(rfbClientPtr cl, * no update is needed. */ + if (cl->continuousUpdates) + sraRgnOr(cl->requestedRegion, cl->cuRegion); + updateRegion = sraRgnCreateRgn(givenUpdateRegion); if(cl->screen->progressiveSliceHeight>0) { int height=cl->screen->progressiveSliceHeight, @@ -3331,6 +3517,9 @@ rfbSendFramebufferUpdate(rfbClientPtr cl, rfbShowCursor(cl); } + if (!rfbSendRTTPing(cl)) + goto updateFailed; + /* * Now send the update. */ @@ -3582,6 +3771,13 @@ rfbSendFramebufferUpdate(rfbClientPtr cl, if(cl->screen->displayFinishedHook) cl->screen->displayFinishedHook(cl, result); + + if (!rfbSendRTTPing(cl)) + result = FALSE; + + rfbUncorkSock(cl->sock); + rfbUpdatePosition(cl, cl->sockOffset); + return result; } diff --git a/src/libvncserver/sockets.c b/src/libvncserver/sockets.c index edc6712b0..59c1414ac 100644 --- a/src/libvncserver/sockets.c +++ b/src/libvncserver/sockets.c @@ -794,10 +794,12 @@ int rfbReadExact(rfbClientPtr cl,char* buf,int len) return(rfbReadExactTimeout(cl,buf,len,rfbMaxClientWait)); } -inline static unsigned min(unsigned a, unsigned b) +#ifndef min +inline static int min(int a, int b) { return a > b ? b : a; } +#endif /* * SkipExact reads an exact number of bytes on a TCP socket into a temporary @@ -921,7 +923,7 @@ rfbWriteExact(rfbClientPtr cl, return 1; #endif rfbSocket sock = cl->sock; - int n; + int n, bytesWritten = 0; fd_set fds; struct timeval tv; int totalTimeWaited = 0; @@ -972,6 +974,7 @@ rfbWriteExact(rfbClientPtr cl, buf += n; len -= n; + bytesWritten += n; } else if (n == 0) { @@ -1023,6 +1026,7 @@ rfbWriteExact(rfbClientPtr cl, } } UNLOCK(cl->outputMutex); + cl->sockOffset += bytesWritten; return 1; }