[Openvpn-devel,v4] openvpnserv: Add support for multi-instances

Message ID 20171203211654.1044-1-simon@rozman.si
State Accepted
Headers show
Series
  • [Openvpn-devel,v4] openvpnserv: Add support for multi-instances
Related show

Commit Message

Simon Rozman Dec. 3, 2017, 9:16 p.m.
While openvpn.exe can run multiple concurrent processes, openvpnserv.exe
is usually only one single globally unique running process.

This patch extends openvpnserv.exe to support multiple service instances
in parallel allowing side-by-side OpenVPN installations.

Alternate instances must be installed as `SERVICE_WIN32_OWN_PROCESS`
(Type 0x10) and must use the newly introduced service command line
parameter:
-instance <name> <id>
<name> can be `automatic` or `interactive`.

- The service settings will be loaded from `HKLM\Software\OpenVPN<id>`
  registry key.

- The automatic service will use `openvpn<id>_exit_1` exit event.

- The interactive service will accept requests on
  `\\.\pipe\openvpn<id>\service` named pipe, and run IPC with
  openvpn.exe on `\\.\pipe\openvpn<id>\service_<pid>`.

This patch preserves backward compatibility, by defaulting to
`SERVICE_WIN32_SHARE_PROCESS` and `<empty string>` as service ID.
---
 src/openvpnserv/automatic.c   | 40 ++++++++++-----------
 src/openvpnserv/common.c      | 16 +++++----
 src/openvpnserv/interactive.c | 20 ++++++++---
 src/openvpnserv/service.c     | 81 +++++++++++++++++++++++++++++++------------
 src/openvpnserv/service.h     |  4 ++-
 5 files changed, 106 insertions(+), 55 deletions(-)

Comments

Selva Nair Dec. 4, 2017, 5:27 p.m. | #1
Hi,

v4 looks good and passes my tests and "opinionated" demands.

Thanks, Simon.

On Sun, Dec 3, 2017 at 4:16 PM, Simon Rozman <simon@rozman.si> wrote:
>
> While openvpn.exe can run multiple concurrent processes, openvpnserv.exe
> is usually only one single globally unique running process.
>
> This patch extends openvpnserv.exe to support multiple service instances
> in parallel allowing side-by-side OpenVPN installations.
>
> Alternate instances must be installed as `SERVICE_WIN32_OWN_PROCESS`
> (Type 0x10) and must use the newly introduced service command line
> parameter:
> -instance <name> <id>
> <name> can be `automatic` or `interactive`.
>
> - The service settings will be loaded from `HKLM\Software\OpenVPN<id>`
>   registry key.
>
> - The automatic service will use `openvpn<id>_exit_1` exit event.
>
> - The interactive service will accept requests on
>   `\\.\pipe\openvpn<id>\service` named pipe, and run IPC with
>   openvpn.exe on `\\.\pipe\openvpn<id>\service_<pid>`.
>
> This patch preserves backward compatibility, by defaulting to
> `SERVICE_WIN32_SHARE_PROCESS` and `<empty string>` as service ID.
> ---


Although this is a new feature, the original behaviour is unaffected and
the possibility of named instances will help projects like eduVPN use
official binary releases.

So, I recommend this for 2.4 as well..

Reviewed by: SelvaNair <selva.nair@gmail.com>
Acked-by: Selva Nair <selva.nair@gmail.com>

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
Gert Doering Dec. 4, 2017, 5:59 p.m. | #2
I've done a bit of staring at the code as well, and it seems to make
sense (but thanks to Selva for a thorough review and actually testing
this :-) ).

Given the interaction with EduVPN 2.4, and the fairly well localized
changes, I agree to Selva's suggestion of having it in 2.4 as well.

Your patch has been applied to the master branch and release/2.4 branch.

commit f3fec49b1c916a701058ef2445b4c07005c30673 (master)
commit 8a52d07c844d718c7032c4936e0c43f0e3e6033d (release/2.4)
Author: Simon Rozman
Date:   Sun Dec 3 22:16:54 2017 +0100

     openvpnserv: Add support for multi-instances

     Acked-by: Selva Nair <selva.nair@gmail.com>
     Message-Id: <20171203211654.1044-1-simon@rozman.si>
     URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg16002.html
     Signed-off-by: Gert Doering <gert@greenie.muc.de>


--
kind regards,

Gert Doering


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
Simon Rozman Dec. 5, 2017, 11:01 a.m. | #3
Hi,

> I've done a bit of staring at the code as well, and it seems to make sense (but
> thanks to Selva for a thorough review and actually testing this :-) ).
>
> Given the interaction with EduVPN 2.4, and the fairly well localized changes, I
> agree to Selva's suggestion of having it in 2.4 as well.
>
> Your patch has been applied to the master branch and release/2.4 branch.

This is excellent. This allows the eduVPN to switch to official openvpnserv.exe when 2.4.5 is released.

Should we/I document how to setup secondary instance of Interactive service?
I am new here and I would appreciate some guidance where and how to document it. Before I forget. ;)

Best regards,
Simon


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
Samuli Seppänen Dec. 5, 2017, 11:19 a.m. | #4
Il 05/12/2017 13:01, Simon Rozman ha scritto:
> Hi,
> 
>> I've done a bit of staring at the code as well, and it seems to make sense (but
>> thanks to Selva for a thorough review and actually testing this :-) ).
>>
>> Given the interaction with EduVPN 2.4, and the fairly well localized changes, I
>> agree to Selva's suggestion of having it in 2.4 as well.
>>
>> Your patch has been applied to the master branch and release/2.4 branch.
> 
> This is excellent. This allows the eduVPN to switch to official openvpnserv.exe when 2.4.5 is released.
> 
> Should we/I document how to setup secondary instance of Interactive service?
> I am new here and I would appreciate some guidance where and how to document it. Before I forget. ;)
> 

Hi,

I think we should have that documentation somewhere. I think it would be
 of interest mostly to developers. What about creating a new text file
to the repository. For example:

  doc/interactive-service-notes.txt

Thoughts?
Илья Шипицин Dec. 5, 2017, 11:21 a.m. | #5
2017-12-05 16:19 GMT+05:00 Samuli Seppänen <samuli@openvpn.net>:

> Il 05/12/2017 13:01, Simon Rozman ha scritto:
> > Hi,
> >
> >> I've done a bit of staring at the code as well, and it seems to make
> sense (but
> >> thanks to Selva for a thorough review and actually testing this :-) ).
> >>
> >> Given the interaction with EduVPN 2.4, and the fairly well localized
> changes, I
> >> agree to Selva's suggestion of having it in 2.4 as well.
> >>
> >> Your patch has been applied to the master branch and release/2.4 branch.
> >
> > This is excellent. This allows the eduVPN to switch to official
> openvpnserv.exe when 2.4.5 is released.
> >
> > Should we/I document how to setup secondary instance of Interactive
> service?
> > I am new here and I would appreciate some guidance where and how to
> document it. Before I forget. ;)
> >
>
> Hi,
>
> I think we should have that documentation somewhere. I think it would be
>  of interest mostly to developers. What about creating a new text file
> to the repository. For example:
>
>   doc/interactive-service-notes.txt
>

doc/interactive-service-notes.rst ?


>
> Thoughts?
>
> --
> Samuli Seppänen
> Community Manager
> OpenVPN Technologies, Inc
>
> irc freenode net: mattock
>
> ------------------------------------------------------------
> ------------------
> Check out the vibrant tech community on one of the world's most
> engaging tech sites, Slashdot.org! http://sdm.link/slashdot
> _______________________________________________
> Openvpn-devel mailing list
> Openvpn-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/openvpn-devel
>
<div dir="ltr"><br><div class="gmail_extra"><br><div class="gmail_quote">2017-12-05 16:19 GMT+05:00 Samuli Seppänen <span dir="ltr">&lt;<a href="mailto:samuli@openvpn.net" target="_blank">samuli@openvpn.net</a>&gt;</span>:<br><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><span class="gmail-">Il 05/12/2017 13:01, Simon Rozman ha scritto:<br>
&gt; Hi,<br>
&gt;<br>
&gt;&gt; I&#39;ve done a bit of staring at the code as well, and it seems to make sense (but<br>
&gt;&gt; thanks to Selva for a thorough review and actually testing this :-) ).<br>
&gt;&gt;<br>
&gt;&gt; Given the interaction with EduVPN 2.4, and the fairly well localized changes, I<br>
&gt;&gt; agree to Selva&#39;s suggestion of having it in 2.4 as well.<br>
&gt;&gt;<br>
&gt;&gt; Your patch has been applied to the master branch and release/2.4 branch.<br>
&gt;<br>
&gt; This is excellent. This allows the eduVPN to switch to official openvpnserv.exe when 2.4.5 is released.<br>
&gt;<br>
&gt; Should we/I document how to setup secondary instance of Interactive service?<br>
&gt; I am new here and I would appreciate some guidance where and how to document it. Before I forget. ;)<br>
&gt;<br>
<br>
</span>Hi,<br>
<br>
I think we should have that documentation somewhere. I think it would be<br>
 of interest mostly to developers. What about creating a new text file<br>
to the repository. For example:<br>
<br>
  doc/interactive-service-notes.<wbr>txt<br></blockquote><div><br>doc/interactive-service-notes.<wbr>rst ?<br> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
Thoughts?<br>
<span class="gmail-HOEnZb"><font color="#888888"><br>
--<br>
Samuli Seppänen<br>
Community Manager<br>
OpenVPN Technologies, Inc<br>
<br>
irc freenode net: mattock<br>
</font></span><div class="gmail-HOEnZb"><div class="gmail-h5"><br>
------------------------------<wbr>------------------------------<wbr>------------------<br>
Check out the vibrant tech community on one of the world&#39;s most<br>
engaging tech sites, Slashdot.org! <a href="http://sdm.link/slashdot" rel="noreferrer" target="_blank">http://sdm.link/slashdot</a><br>
______________________________<wbr>_________________<br>
Openvpn-devel mailing list<br>
<a href="mailto:Openvpn-devel@lists.sourceforge.net">Openvpn-devel@lists.<wbr>sourceforge.net</a><br>
<a href="https://lists.sourceforge.net/lists/listinfo/openvpn-devel" rel="noreferrer" target="_blank">https://lists.sourceforge.net/<wbr>lists/listinfo/openvpn-devel</a><br>
</div></div></blockquote></div><br></div></div>
------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot

Patch

diff --git a/src/openvpnserv/automatic.c b/src/openvpnserv/automatic.c
index 4123d0f..75c3be2 100644
--- a/src/openvpnserv/automatic.c
+++ b/src/openvpnserv/automatic.c
@@ -44,7 +44,7 @@ 
 #define false 0
 
 static SERVICE_STATUS_HANDLE service;
-static SERVICE_STATUS status;
+static SERVICE_STATUS status = { .dwServiceType = SERVICE_WIN32_SHARE_PROCESS };
 
 openvpn_service_t automatic_service = {
     automatic,
@@ -60,12 +60,6 @@  struct security_attributes
     SECURITY_DESCRIPTOR sd;
 };
 
-/*
- * Which registry key in HKLM should
- * we get config info from?
- */
-#define REG_KEY "SOFTWARE\\" PACKAGE_NAME
-
 static HANDLE exit_event = NULL;
 
 /* clear an object */
@@ -91,15 +85,6 @@  init_security_attributes_allow_all(struct security_attributes *obj)
     return true;
 }
 
-/*
- * This event is initially created in the non-signaled
- * state.  It will transition to the signaled state when
- * we have received a terminate signal from the Service
- * Control Manager which will cause an asynchronous call
- * of ServiceStop below.
- */
-#define EXIT_EVENT_NAME  TEXT(PACKAGE "_exit_1")
-
 HANDLE
 create_event(LPCTSTR name, bool allow_all, bool initial_state, bool manual_reset)
 {
@@ -212,10 +197,19 @@  ServiceCtrlAutomatic(DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx)
 
 
 VOID WINAPI
+ServiceStartAutomaticOwn(DWORD dwArgc, LPTSTR *lpszArgv)
+{
+    status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+    ServiceStartAutomatic(dwArgc, lpszArgv);
+}
+
+
+VOID WINAPI
 ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
 {
     DWORD error = NO_ERROR;
     settings_t settings;
+    TCHAR event_name[256];
 
     service = RegisterServiceCtrlHandlerEx(automatic_service.name, ServiceCtrlAutomatic, &status);
     if (!service)
@@ -223,7 +217,6 @@  ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
         return;
     }
 
-    status.dwServiceType = SERVICE_WIN32_SHARE_PROCESS;
     status.dwCurrentState = SERVICE_START_PENDING;
     status.dwServiceSpecificExitCode = NO_ERROR;
     status.dwWin32ExitCode = NO_ERROR;
@@ -237,8 +230,15 @@  ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
 
     /*
      * Create our exit event
+     * This event is initially created in the non-signaled
+     * state.  It will transition to the signaled state when
+     * we have received a terminate signal from the Service
+     * Control Manager which will cause an asynchronous call
+     * of ServiceStop below.
      */
-    exit_event = create_event(EXIT_EVENT_NAME, false, false, true);
+
+    openvpn_sntprintf(event_name, _countof(event_name), TEXT(PACKAGE "%s_exit_1"), service_instance);
+    exit_event = create_event(event_name, false, false, true);
     if (!exit_event)
     {
         MsgToEventLog(M_ERR, TEXT("CreateEvent failed"));
@@ -327,8 +327,8 @@  ServiceStartAutomatic(DWORD dwArgc, LPTSTR *lpszArgv)
                                   TEXT("%s\\%s"), settings.log_dir, log_file);
 
                 /* construct command line */
-                openvpn_sntprintf(command_line, _countof(command_line), TEXT(PACKAGE " --service %s 1 --config \"%s\""),
-                                  EXIT_EVENT_NAME,
+                openvpn_sntprintf(command_line, _countof(command_line), TEXT("openvpn --service \"" PACKAGE "%s_exit_1\" 1 --config \"%s\""),
+                                  service_instance,
                                   find_obj.cFileName);
 
                 /* Make security attributes struct for logfile handle so it can
diff --git a/src/openvpnserv/common.c b/src/openvpnserv/common.c
index e77d7ab..d2c7f4a 100644
--- a/src/openvpnserv/common.c
+++ b/src/openvpnserv/common.c
@@ -24,6 +24,9 @@ 
 #include "service.h"
 #include "validate.h"
 
+LPCTSTR service_instance = TEXT("");
+
+
 /*
  * These are necessary due to certain buggy implementations of (v)snprintf,
  * that don't guarantee null termination for size > 0.
@@ -53,8 +56,6 @@  openvpn_sntprintf(LPTSTR str, size_t size, LPCTSTR format, ...)
     return len;
 }
 
-#define REG_KEY  TEXT("SOFTWARE\\" PACKAGE_NAME)
-
 static DWORD
 GetRegString(HKEY key, LPCTSTR value, LPTSTR data, DWORD size)
 {
@@ -69,7 +70,7 @@  GetRegString(HKEY key, LPCTSTR value, LPTSTR data, DWORD size)
     if (status != ERROR_SUCCESS)
     {
         SetLastError(status);
-        return MsgToEventLog(M_SYSERR, TEXT("Error querying registry value: HKLM\\%s\\%s"), REG_KEY, value);
+        return MsgToEventLog(M_SYSERR, TEXT("Error querying registry value: HKLM\\SOFTWARE\\" PACKAGE_NAME "%s\\%s"), service_instance, value);
     }
 
     return ERROR_SUCCESS;
@@ -79,16 +80,19 @@  GetRegString(HKEY key, LPCTSTR value, LPTSTR data, DWORD size)
 DWORD
 GetOpenvpnSettings(settings_t *s)
 {
+    TCHAR reg_path[256];
     TCHAR priority[64];
     TCHAR append[2];
     DWORD error;
     HKEY key;
 
-    LONG status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, REG_KEY, 0, KEY_READ, &key);
+    openvpn_sntprintf(reg_path, _countof(reg_path), TEXT("SOFTWARE\\" PACKAGE_NAME "%s"), service_instance);
+
+    LONG status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, reg_path, 0, KEY_READ, &key);
     if (status != ERROR_SUCCESS)
     {
         SetLastError(status);
-        return MsgToEventLog(M_SYSERR, TEXT("Could not open Registry key HKLM\\%s not found"), REG_KEY);
+        return MsgToEventLog(M_SYSERR, TEXT("Could not open Registry key HKLM\\%s not found"), reg_path);
     }
 
     error = GetRegString(key, TEXT("exe_path"), s->exe_path, sizeof(s->exe_path));
@@ -232,7 +236,7 @@  MsgToEventLog(DWORD flags, LPCTSTR format, ...)
     if (hEventSource != NULL)
     {
         openvpn_sntprintf(msg[0], _countof(msg[0]),
-                          TEXT("%s%s: %s"), APPNAME,
+                          TEXT("%s%s%s: %s"), APPNAME, service_instance,
                           (flags & MSG_FLAGS_ERROR) ? TEXT(" error") : TEXT(""), err_msg);
 
         va_start(arglist, format);
diff --git a/src/openvpnserv/interactive.c b/src/openvpnserv/interactive.c
index 0d162e8..5832d35 100644
--- a/src/openvpnserv/interactive.c
+++ b/src/openvpnserv/interactive.c
@@ -53,7 +53,7 @@ 
 #define ERROR_MESSAGE_TYPE     0x20000003
 
 static SERVICE_STATUS_HANDLE service;
-static SERVICE_STATUS status;
+static SERVICE_STATUS status = { .dwServiceType = SERVICE_WIN32_SHARE_PROCESS };
 static HANDLE exit_event = NULL;
 static settings_t settings;
 static HANDLE rdns_semaphore = NULL;
@@ -1325,7 +1325,7 @@  RunOpenvpn(LPVOID p)
     STARTUPINFOW startup_info;
     PROCESS_INFORMATION proc_info;
     LPVOID user_env = NULL;
-    TCHAR ovpn_pipe_name[36];
+    TCHAR ovpn_pipe_name[256]; /* The entire pipe name string can be up to 256 characters long according to MSDN. */
     LPCWSTR exe_path;
     WCHAR *cmdline = NULL;
     size_t cmdline_size;
@@ -1487,7 +1487,7 @@  RunOpenvpn(LPVOID p)
     }
 
     openvpn_sntprintf(ovpn_pipe_name, _countof(ovpn_pipe_name),
-                      TEXT("\\\\.\\pipe\\openvpn\\service_%lu"), GetCurrentThreadId());
+                      TEXT("\\\\.\\pipe\\" PACKAGE "%s\\service_%lu"), service_instance, GetCurrentThreadId());
     ovpn_pipe = CreateNamedPipe(ovpn_pipe_name,
                                 PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE | FILE_FLAG_OVERLAPPED,
                                 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 1, 128, 128, 0, NULL);
@@ -1654,6 +1654,7 @@  ServiceCtrlInteractive(DWORD ctrl_code, DWORD event, LPVOID data, LPVOID ctx)
 static HANDLE
 CreateClientPipeInstance(VOID)
 {
+    TCHAR pipe_name[256]; /* The entire pipe name string can be up to 256 characters long according to MSDN. */
     HANDLE pipe = NULL;
     PACL old_dacl, new_dacl;
     PSECURITY_DESCRIPTOR sd;
@@ -1690,7 +1691,8 @@  CreateClientPipeInstance(VOID)
         initialized = TRUE;
     }
 
-    pipe = CreateNamedPipe(TEXT("\\\\.\\pipe\\openvpn\\service"), flags,
+    openvpn_sntprintf(pipe_name, _countof(pipe_name), TEXT("\\\\.\\pipe\\" PACKAGE "%s\\service"), service_instance);
+    pipe = CreateNamedPipe(pipe_name, flags,
                            PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,
                            PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, NULL);
     if (pipe == INVALID_HANDLE_VALUE)
@@ -1785,6 +1787,15 @@  CmpHandle(LPVOID item, LPVOID hnd)
     return item == hnd;
 }
 
+
+VOID WINAPI
+ServiceStartInteractiveOwn(DWORD dwArgc, LPTSTR *lpszArgv)
+{
+    status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+    ServiceStartInteractive(dwArgc, lpszArgv);
+}
+
+
 VOID WINAPI
 ServiceStartInteractive(DWORD dwArgc, LPTSTR *lpszArgv)
 {
@@ -1801,7 +1812,6 @@  ServiceStartInteractive(DWORD dwArgc, LPTSTR *lpszArgv)
         return;
     }
 
-    status.dwServiceType = SERVICE_WIN32_SHARE_PROCESS;
     status.dwCurrentState = SERVICE_START_PENDING;
     status.dwServiceSpecificExitCode = NO_ERROR;
     status.dwWin32ExitCode = NO_ERROR;
diff --git a/src/openvpnserv/service.c b/src/openvpnserv/service.c
index b79e999..7157bea 100644
--- a/src/openvpnserv/service.c
+++ b/src/openvpnserv/service.c
@@ -223,46 +223,81 @@  out:
 int
 _tmain(int argc, TCHAR *argv[])
 {
-    SERVICE_TABLE_ENTRY dispatchTable[] = {
+    /*
+     * Automatic + Interactive service (as a SERVICE_WIN32_SHARE_PROCESS)
+     * This is the default.
+     */
+    const SERVICE_TABLE_ENTRY dispatchTable_shared[] = {
         { automatic_service.name, ServiceStartAutomatic },
         { interactive_service.name, ServiceStartInteractive },
         { NULL, NULL }
     };
 
+    /* Automatic service only (as a SERVICE_WIN32_OWN_PROCESS) */
+    const SERVICE_TABLE_ENTRY dispatchTable_automatic[] = {
+        { TEXT(""), ServiceStartAutomaticOwn },
+        { NULL, NULL }
+    };
+
+    /* Interactive service only (as a SERVICE_WIN32_OWN_PROCESS) */
+    const SERVICE_TABLE_ENTRY dispatchTable_interactive[] = {
+        { TEXT(""), ServiceStartInteractiveOwn },
+        { NULL, NULL }
+    };
+
+    const SERVICE_TABLE_ENTRY *dispatchTable = dispatchTable_shared;
+
     openvpn_service[0] = automatic_service;
     openvpn_service[1] = interactive_service;
 
-    if (argc > 1 && (*argv[1] == TEXT('-') || *argv[1] == TEXT('/')))
+    for (int i = 1; i < argc; i++)
     {
-        if (_tcsicmp(TEXT("install"), argv[1] + 1) == 0)
-        {
-            return CmdInstallServices();
-        }
-        else if (_tcsicmp(TEXT("remove"), argv[1] + 1) == 0)
-        {
-            return CmdRemoveServices();
-        }
-        else if (_tcsicmp(TEXT("start"), argv[1] + 1) == 0)
-        {
-            BOOL is_auto = argc < 3 || _tcsicmp(TEXT("interactive"), argv[2]) != 0;
-            return CmdStartService(is_auto ? automatic : interactive);
-        }
-        else
+        if (*argv[i] == TEXT('-') || *argv[i] == TEXT('/'))
         {
-            goto dispatch;
-        }
+            if (_tcsicmp(TEXT("install"), argv[i] + 1) == 0)
+            {
+                return CmdInstallServices();
+            }
+            else if (_tcsicmp(TEXT("remove"), argv[i] + 1) == 0)
+            {
+                return CmdRemoveServices();
+            }
+            else if (_tcsicmp(TEXT("start"), argv[i] + 1) == 0)
+            {
+                BOOL is_auto = argc < i + 2 || _tcsicmp(TEXT("interactive"), argv[i + 1]) != 0;
+                return CmdStartService(is_auto ? automatic : interactive);
+            }
+            else if (argc > i + 2 && _tcsicmp(TEXT("instance"), argv[i] + 1) == 0)
+            {
+                dispatchTable = _tcsicmp(TEXT("interactive"), argv[i + 1]) != 0 ?
+                    dispatchTable_automatic :
+                    dispatchTable_interactive;
 
-        return 0;
+                service_instance = argv[i + 2];
+                i += 2;
+            }
+            else
+            {
+                _tprintf(TEXT("%s -install        to install the services\n"), APPNAME);
+                _tprintf(TEXT("%s -start <name>   to start a service (\"automatic\" or \"interactive\")\n"), APPNAME);
+                _tprintf(TEXT("%s -remove         to remove the services\n"), APPNAME);
+
+                _tprintf(TEXT("\nService run-time parameters:\n"));
+                _tprintf(TEXT("-instance <name> <id>\n")
+                         TEXT("   Runs the service as an alternate instance. <name> can be \"automatic\" or\n")
+                         TEXT("   \"interactive\". The service settings will be loaded from\n")
+                         TEXT("   HKLM\\Software\\" PACKAGE_NAME "<id> registry key, and the interactive service will accept\n")
+                         TEXT("   requests on \\\\.\\pipe\\" PACKAGE "<id>\\service named pipe.\n"));
+
+                return 0;
+            }
+        }
     }
 
     /* If it doesn't match any of the above parameters
      * the service control manager may be starting the service
      * so we must call StartServiceCtrlDispatcher
      */
-dispatch:
-    _tprintf(TEXT("%s -install        to install the services\n"), APPNAME);
-    _tprintf(TEXT("%s -start <name>   to start a service (\"automatic\" or \"interactive\")\n"), APPNAME);
-    _tprintf(TEXT("%s -remove         to remove the services\n"), APPNAME);
     _tprintf(TEXT("\nStartServiceCtrlDispatcher being called.\n"));
     _tprintf(TEXT("This may take several seconds. Please wait.\n"));
 
diff --git a/src/openvpnserv/service.h b/src/openvpnserv/service.h
index 9fe573e..1afd20e 100644
--- a/src/openvpnserv/service.h
+++ b/src/openvpnserv/service.h
@@ -73,10 +73,12 @@  typedef struct {
 
 extern openvpn_service_t automatic_service;
 extern openvpn_service_t interactive_service;
+extern LPCTSTR service_instance;
 
-
+VOID WINAPI ServiceStartAutomaticOwn(DWORD argc, LPTSTR *argv);
 VOID WINAPI ServiceStartAutomatic(DWORD argc, LPTSTR *argv);
 
+VOID WINAPI ServiceStartInteractiveOwn(DWORD argc, LPTSTR *argv);
 VOID WINAPI ServiceStartInteractive(DWORD argc, LPTSTR *argv);
 
 int openvpn_vsntprintf(LPTSTR str, size_t size, LPCTSTR format, va_list arglist);