]> www.infradead.org Git - users/dwmw2/openconnect.git/commitdiff
Improved adapter name generation when no adapter name is specified.
authorMarios Paouris <mspaourh@gmail.com>
Sat, 12 Oct 2024 14:56:34 +0000 (17:56 +0300)
committerMarios Paouris <mspaourh@gmail.com>
Tue, 5 Nov 2024 07:16:50 +0000 (09:16 +0200)
Try to find an adapter name that is not already used in the system by
appending a monotonically increasing integer to the hostname that is
used as a default name.

This works around wintun's weird behaviour of renaming existing adapters
without preventing two or more instances of openconnect to connect to
the same VPN host (without explicity specifying an interface name), or
otherwise messing with user's network adapters.

Signed-off-by: Marios Paouris <mspaourh@gmail.com>
tests/Makefile.am
tests/adapter-fallback.c [new file with mode: 0644]
tun-win32.c

index c03671f29ff3e5d08353259176dd7047cca213b9..907652726286f63ed567b11ff976172761f927f3 100644 (file)
@@ -154,9 +154,10 @@ C_TESTS = lzstest seqtest buftest
 DISTCLEANFILES =
 
 if OPENCONNECT_WIN32
-C_TESTS += list-taps
+C_TESTS += list-taps adapter-fallback
 
 list_taps_SOURCES = list-taps.c
+adapter_fallback_SOURCES = adapter-fallback.c
 
 if OPENCONNECT_WINTUN
 C_TESTS += wintun-names
diff --git a/tests/adapter-fallback.c b/tests/adapter-fallback.c
new file mode 100644 (file)
index 0000000..914d199
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ * OpenConnect (SSL + DTLS) VPN client
+ *
+  * Copyright © 2024 Marios Paouris
+ *
+ * Author: Marios Paouris <mspaourh@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * 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
+ * Lesser General Public License for more details.
+ */
+
+#include <config.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <fcntl.h>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#define __OPENCONNECT_INTERNAL_H__
+
+#define PRG_ERR                0
+#define PRG_INFO       1
+#define PRG_DEBUG      2
+#define PRG_TRACE      3
+
+#define vpn_progress(v, d, ...) do { \
+  if ( d < PRG_TRACE) { \
+    printf(__VA_ARGS__); \
+  } \
+} while (0);
+
+#define _(x) x
+
+#define LIST_APPEND(__list, __end, __new) do { \
+    if (!__list) \
+        __list = __new; \
+    if (__end) \
+        __end->next = __new; \
+    __end = __new; \
+} while (0);
+
+struct openconnect_info {
+       char *ifname;
+    wchar_t *ifname_w;
+};
+
+/* don't link linkopenconnect in this test, just for this function
+ * it won't get loaded under wine when cross compiling anyway */
+#define openconnect__win32_strerror(err) (strdup("(Actual error text not present in tests)"))
+
+#define OPEN_TUN_SOFTFAIL 0
+#define OPEN_TUN_HARDFAIL -1
+
+#define __LIST_TAPS__
+
+#define MAX_FALLBACK_TRIES 15
+
+#include "../tun-win32.c"
+
+#define NORMAL_CASE L"www.vpnserver.org"
+#define EDGE_CASE L"AVeryLongAdapterWhoseNameConsistsOfExactlyOneHundredTwentySevenCharactersAndNeedsTooManyWordsToSatifyThisLoooongParticularLimit"
+#define LONG_CASE L"AnotherVeryLongAdapterWhoseNameConsistsOfExactlyOneHundredTwentySevenCharactersAndNeedsTooManyWordsToSatifyThisLoooongParticularLimit+PlusMoreToTruncate"
+#define LONG_CASE_EXPECT L"AnotherVeryLongAdapterWhoseNameConsistsOfExactlyOneHundredTwentySevenCharactersAndNeedsTooManyWordsToSatifyThisLoooongParticula"
+#define WC_ZERO 0xfeff0030 /* DIGIT ZERO (U+0030) */
+#define WC_GCLEF_HIGH 0xD834 /* U+1D11E Musical Symbol G Clef = 0xD834 0xDD1E */
+#define WC_GCLEF_LOW  0xDD1E /* U+1D11E Musical Symbol G Clef = 0xD834 0xDD1E */
+
+static void CALLBACK
+ConsoleLogger(_In_z_ const WCHAR *LogLine)
+{
+    fwprintf(
+        stdout,
+        L"%ls\n",
+        LogLine);
+}
+
+static void
+Log(_In_z_ const WCHAR *Format, ...)
+{
+    WCHAR LogLine[0x200];
+    va_list args;
+    va_start(args, Format);
+    _vsnwprintf_s(LogLine, _countof(LogLine), _TRUNCATE, Format, args);
+    va_end(args);
+    ConsoleLogger(LogLine);
+}
+
+/* iteratively try to find an adapter name with a given prefix.
+   after each try, add the adapter to the list so the next try will find it in the list
+   and generate another name,
+   Repeat until max tries has been reached and no adapter name can be found
+ */
+
+static int test_repeated(wchar_t* prefix, int max_tries, wchar_t expectedNames[][MAX_ADAPTER_NAME])
+{
+    struct openconnect_info empty_info = { NULL, NULL};
+    struct oc_adapter_info *list = NULL, *end = NULL;
+    int ret = 0;
+    Log(L"Test prefix  : %s for %d tries", prefix, max_tries);
+
+    for (int i = 0; i <= (max_tries + 1); i++) {
+        wchar_t * adapterName = first_available_adapter_name(&empty_info, list, prefix);
+
+        if (adapterName) {
+            Log(L"%2d: available: %s\n" \
+                 L"     expected: %s", i, adapterName, (i < max_tries ? expectedNames[i]: L""));
+
+            struct oc_adapter_info *new = adapter_alloc(&empty_info, ADAPTER_NONE, adapterName, "");
+            LIST_APPEND(list, end, new);
+
+            if (i == max_tries) {
+                Log(L"Available adapter name was not expected: %s", adapterName);
+                ret = 1;
+                goto out;
+            }
+
+            if (wcscmp(expectedNames[i], adapterName)) {
+                Log(L"Available adapter name does not match expected adapter name");
+                ret = 1;
+                goto out;
+            }
+        }
+        else {
+            if ( i != max_tries) {
+                Log(L"Could not find an available adapter name, expected %s", expectedNames[i]);
+                ret = 2;
+                goto out;
+            }
+            i = max_tries + 10;
+        }
+    }
+
+out:
+    free_adapter_list(list);
+    Log(L"");
+
+    return ret;
+}
+
+static int test_single(wchar_t *prefix, wchar_t *expectedName)
+{
+    struct openconnect_info empty_info = { NULL, NULL};
+    struct oc_adapter_info *list = NULL;
+    int ret = 0;
+    Log(L"Test prefix  : %s", prefix);
+
+    wchar_t * adapterName = first_available_adapter_name(&empty_info, list, prefix);
+
+    if (adapterName) {
+        Log(L"  : available: %s\n" \
+            L"     expected: %s", adapterName, expectedName);
+
+        struct oc_adapter_info *new = adapter_alloc(&empty_info, ADAPTER_NONE, adapterName, "");
+
+        if (wcscmp(expectedName, adapterName)) {
+            Log(L"Available adapter name does not match expected adapter name");
+            ret = 5;
+            goto out;
+        }
+
+        free(adapterName);
+    }
+    else {
+        Log(L"Could not find an available adapter name, expected %s", expectedName);
+        ret = 6;
+    }
+
+out:
+    Log(L"");
+    return ret;
+}
+
+int main(void)
+{
+    _setmode(_fileno(stdout), _O_U16TEXT);
+
+    int ret = 0;
+
+    wchar_t expectedNames[MAX_FALLBACK_TRIES + 1][MAX_ADAPTER_NAME];
+
+    /* test the common case: a vpn url with a normal length (less than MAX_ADAPTER_NAME). */
+    memset(expectedNames, 0, sizeof(expectedNames));
+    wcsncpy(expectedNames[0], NORMAL_CASE, MAX_ADAPTER_NAME - 1);
+    for (int i = 1; i <= MAX_FALLBACK_TRIES; i++) {
+        _snwprintf(expectedNames[i], MAX_ADAPTER_NAME - 1, L"%s%d", NORMAL_CASE, i);
+    }
+
+    ret = test_repeated(NORMAL_CASE, MAX_FALLBACK_TRIES, expectedNames);
+
+    if (ret) {
+        return 1;
+    }
+
+    /* test an edge case: a name with the maximum allowed length (MAX_ADAPTER_NAME - 1). */
+    memset(expectedNames, 0, sizeof(expectedNames));
+    wcsncpy(expectedNames[0], EDGE_CASE, MAX_ADAPTER_NAME - 1);
+    for (int i = 1; i <= MAX_FALLBACK_TRIES; i++) {
+        wcsncpy(expectedNames[i], EDGE_CASE, MAX_ADAPTER_NAME - 1);
+        int rem = i % 10;
+        expectedNames[i][MAX_ADAPTER_NAME -2] = (WC_ZERO + rem);
+        int quot = i / 10;
+        if (quot > 0) {
+            expectedNames[i][MAX_ADAPTER_NAME -3] = (WC_ZERO + quot);
+        }
+    }
+
+    ret = test_repeated(EDGE_CASE, MAX_FALLBACK_TRIES, expectedNames);
+
+    if (ret) {
+        return 2;
+    }
+
+
+    /* test the case where the name needs to be truncated*/
+    ret = test_single(LONG_CASE, LONG_CASE_EXPECT);
+    if (ret) {
+        return 3;
+    }
+
+    /* test the case when the name needs to be truncated and a surrogate pair is present at the truncation point */
+    size_t len = sizeof(LONG_CASE)/sizeof(wchar_t) - 1;
+    wchar_t adapterName[sizeof(LONG_CASE) + 1];
+    memset(adapterName, 0, sizeof(adapterName));
+    wcsncpy(adapterName, LONG_CASE, len);
+    adapterName[len + 1] = 0;
+    adapterName[MAX_ADAPTER_NAME - 2] = WC_GCLEF_HIGH ;
+    adapterName[MAX_ADAPTER_NAME - 1] = WC_GCLEF_LOW ;
+
+    memset(expectedNames, 0, sizeof(expectedNames));
+    wcsncpy(expectedNames[0], LONG_CASE, MAX_ADAPTER_NAME - 2);
+
+    ret = test_single(adapterName, expectedNames[0]);
+    if (ret) {
+        return 4;
+    }
+
+    /* test the case when the name needs to be truncated and a surrogate pair is present just before the truncation point */
+    memset(adapterName, 0, sizeof(adapterName));
+    wcsncpy(adapterName, LONG_CASE, len);
+    adapterName[len + 1] = 0;
+    adapterName[MAX_ADAPTER_NAME - 3] = WC_GCLEF_HIGH;
+    adapterName[MAX_ADAPTER_NAME - 2] = WC_GCLEF_LOW;
+
+    memset(expectedNames, 0, sizeof(expectedNames));
+    wcsncpy(expectedNames[0], adapterName, MAX_ADAPTER_NAME - 1);
+
+    for (int i = 1 ; i <= MAX_FALLBACK_TRIES; i++) {
+        wcsncpy(expectedNames[i], expectedNames[0], MAX_ADAPTER_NAME - 1);
+        int rem = i % 10;
+        int quot = i / 10;
+
+        expectedNames[i][MAX_ADAPTER_NAME - 3] = WC_ZERO + ( quot > 0 ? quot: rem ); 
+        expectedNames[i][MAX_ADAPTER_NAME - 2] = ( quot > 0 ? WC_ZERO + rem : 0); 
+        expectedNames[i][MAX_ADAPTER_NAME - 1] = 0;
+    }
+
+    ret = test_repeated(adapterName, MAX_FALLBACK_TRIES, expectedNames);
+    if (ret) {
+        return 5;
+    }
+
+    return ret;
+}
index 39ce0da7a695cf6392aef54398a252eabebbb32f..59dd1e6b5f973590b8be1d3307413b3ed17a86c9 100644 (file)
@@ -2,8 +2,10 @@
  * OpenConnect (SSL + DTLS) VPN client
  *
  * Copyright © 2008-2015 Intel Corporation.
+ * Copyright © 2024 Marios Paouris
  *
- * Author: David Woodhouse <dwmw2@infradead.org>
+ * Authors: David Woodhouse <dwmw2@infradead.org>
+ *          Marios Paouris <mspaourh@gmail.com>
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public License
@@ -30,6 +32,7 @@
 
 #include <errno.h>
 #include <stdio.h>
+#include <assert.h>
 
 /*
  * TAP-Windows support inspired by http://i3.cs.berkeley.edu/ (v0.2) with
@@ -71,6 +74,10 @@ typedef intptr_t (tap_callback)(struct openconnect_info *vpninfo, int type, char
 #define SEARCH_CONTINUE        0
 #define SEARCH_DONE    1
 
+#ifndef MAX_FALLBACK_TRIES
+#define MAX_FALLBACK_TRIES 15
+#endif
+
 /* a linked list of adapter information */
 struct oc_adapter_info {
        int type;
@@ -126,6 +133,67 @@ static struct oc_adapter_info * find_adapter_by_name(struct openconnect_info *vp
        return found;
 }
 
+static wchar_t * first_available_adapter_name(struct openconnect_info *vpninfo, struct oc_adapter_info *list, const wchar_t *prefix)
+{
+       wchar_t buf[MAX_ADAPTER_NAME];
+       wchar_t usedPrefix[MAX_ADAPTER_NAME];
+       wchar_t *adapterName;
+       wchar_t * ret = NULL;
+
+       size_t prefix_len = wcslen(prefix);
+
+       /* safeguard against prefix being too large */
+       if (prefix_len > (MAX_ADAPTER_NAME - 1)) {
+               prefix_len = MAX_ADAPTER_NAME - 1;
+       }
+
+       /* copy the safe prefix */
+       wcsncpy(usedPrefix, prefix, prefix_len);
+
+       /* without splitting surrogate pairs */
+       if (IS_HIGH_SURROGATE(usedPrefix[prefix_len - 1])) {
+               prefix_len--;
+       }
+       usedPrefix[prefix_len] = 0;
+
+       adapterName = usedPrefix;
+
+       /* don't set count more than 2 digits */
+       assert((MAX_FALLBACK_TRIES <= 99));
+
+       int count = MAX_FALLBACK_TRIES;
+       int digits_count = 0;
+       int tries = 0;
+       while (tries < count) {
+               struct oc_adapter_info * found = find_adapter_by_name(vpninfo, list, adapterName);
+               if (!found) {
+                       ret = wcsdup(adapterName);
+                       break;
+               }
+
+               /* no adapter found; append try count and try again */
+               tries++;
+
+               if (tries == 1 || tries == 10) {
+                       /* make room for one more digit */
+                       digits_count++;
+               }
+               if (prefix_len + digits_count > (MAX_ADAPTER_NAME - 1)) {
+                       prefix_len--;
+                       /* without splitting surrogate pairs */
+                       if (IS_HIGH_SURROGATE(usedPrefix[prefix_len - 1])) {
+                               prefix_len--;
+                       }
+                       usedPrefix[prefix_len] = 0;
+               }
+
+               _snwprintf(buf, digits_count + prefix_len + 1, L"%s%d", usedPrefix, tries);
+               adapterName = buf;
+       }
+
+       return ret;
+}
+
 static intptr_t search_taps(struct openconnect_info *vpninfo, struct oc_adapter_info *adapter_list, tap_callback *cb)
 {
        struct oc_adapter_info *this = adapter_list;
@@ -685,7 +753,7 @@ static intptr_t create_ifname_w(struct openconnect_info *vpninfo,
        buf_append_utf16le(ifname_buf, ifname);
 
        if (buf_error(ifname_buf)) {
-               vpn_progress(vpninfo, PRG_ERR, _("Could not construct interface name\n"));
+               vpn_progress(vpninfo, PRG_ERR, _("Could not construct interface name for \"%s\"\n"), ifname);
                return buf_free(ifname_buf);
        }
 
@@ -698,7 +766,9 @@ static intptr_t create_ifname_w(struct openconnect_info *vpninfo,
 
 static void clear_ifname_w(struct openconnect_info *vpninfo)
 {
-       free(vpninfo->ifname_w);
+       if (vpninfo->ifname_w) {
+               free(vpninfo->ifname_w);
+       }
        vpninfo->ifname_w = NULL;
 }
 
@@ -739,25 +809,27 @@ intptr_t os_setup_tun(struct openconnect_info *vpninfo)
                }
        }
        else {
-               /* the user did not specify an interface name; try create a wintun default based on hostname */
-               ret = create_ifname_w(vpninfo, vpninfo->hostname);
-               if (ret)
-                       goto safe_return;
-
-               /* check if the adapter already exists */
-               struct oc_adapter_info * new = find_adapter_by_name(vpninfo, list, vpninfo->ifname_w);
-
-               if (new) {
-                       /* don't create a wintun adapter with a default name;
-             * unfortunately, wintun will rename an existing adapter when creating an adapter with the same name
-             * see https://git.zx2c4.com/wintun/tree/api/adapter.c?id=41624504341307f7f55afe72e86d5d8c76f81c0e#n292
-                        */
-                       vpn_progress(vpninfo, PRG_INFO,
-                                               _("Adapter %S already exists. Cannot use it as a default wintun adapter.\n"), 
-                                               vpninfo->ifname_w);
+               /* the user did not specify an interface name; try create a wintun default based on hostname
+               * this is also required since, unfortunately, wintun will rename an existing adapter when 
+               * creating an adapter with the same name.
+               * see https://git.zx2c4.com/wintun/tree/api/adapter.c?id=41624504341307f7f55afe72e86d5d8c76f81c0e#n292
+               */
+               struct oc_adapter_info * new = NULL;
+               wchar_t *fallback_ifname = NULL;
+               intptr_t ret_ciw = create_ifname_w(vpninfo, vpninfo->hostname);
+
+               if (!ret_ciw) {
+                       wchar_t *prefix = vpninfo->ifname_w;
+                       vpninfo->ifname_w = NULL;
+                       fallback_ifname = first_available_adapter_name(vpninfo, list, prefix);
+                       free(prefix);
                }
 
-               if ( !new ) {
+               if (!fallback_ifname) {
+                       vpn_progress(vpninfo, PRG_INFO,
+                                               _("Unable to find a usable default adapter name based on hostname.\n"));
+               } else {
+                       vpninfo->ifname_w = fallback_ifname;
                        new = create_wintun_adapter(vpninfo, open_tun, &ret);
                        if ( new ) {
                                /* add the new adapter to the beginning of the list so it can be freed */
@@ -765,12 +837,9 @@ intptr_t os_setup_tun(struct openconnect_info *vpninfo)
                                list = new;
                        }
                }
-               else {
-                       new = NULL;
-               }
 
                if ( !new ) {
-                       /* could not create wintun adapter; cleanup ifname_w and fallback to the first available tap */
+                       /* could not create wintun adapter; cleanup ifname_w and fallback to the first available tap */
                        clear_ifname_w(vpninfo);
                        ret = search_taps(vpninfo, list, open_tun);
                }