Skip to content

Commit

Permalink
Add support for marking connections as persistent
Browse files Browse the repository at this point in the history
Persistent connections have openvpn.exe daemon started
external to the GUI (e.g., by the automatic service).
This patch adds support for attaching to the management
i/f of such daemons from the GUI and control the connection.

The GUI never stops or starts the openvpn.exe process in this
case. Instead, connect and disconnect buttons signal the
management interface of a running openvpn.exe process to start
the tunnel by attaching to mgmt i/f and sending hold-release if
needed  or stop it and wait in management-hold state
(see DisconnectDaemon()).

When the GUI process exits, persistent connections are left in their
current state using DetachOpenVPN().

No connections are marked as persistent as yet. That is done
in a following commit.

Signed-off-by: Selva Nair <[email protected]>
  • Loading branch information
selvanair committed Aug 8, 2022
1 parent f8a1495 commit 428ee29
Show file tree
Hide file tree
Showing 29 changed files with 224 additions and 12 deletions.
24 changes: 22 additions & 2 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -351,10 +351,23 @@ StopAllOpenVPN()
{
int i;

/* Stop all connections started by us -- we leave persistent ones
* at their current state. Use the disconnect menu to put them into
* hold state before exit, if desired.
*/
for (i = 0; i < o.num_configs; i++)
{
if (o.conn[i].state != disconnected)
StopOpenVPN(&o.conn[i]);
{
if (o.conn[i].flags & FLAG_DAEMON_PERSISTENT)
{
DetachOpenVPN(&o.conn[i]);
}
else
{
StopOpenVPN(&o.conn[i]);
}
}
}

/* Wait for all connections to terminate (Max 5 sec) */
Expand Down Expand Up @@ -756,14 +769,21 @@ CloseApplication(HWND hwnd)
&& ShowLocalizedMsgEx(MB_YESNO, NULL, _T("Exit OpenVPN"), IDS_NFO_SERVICE_ACTIVE_EXIT) == IDNO)
return;

/* Show a message if any non-persistent connections are active */
for (i = 0; i < o.num_configs; i++)
{
if (o.conn[i].state == disconnected)
if (o.conn[i].state == disconnected
|| o.conn[i].flags & FLAG_DAEMON_PERSISTENT)
{
continue;
}

/* Ask for confirmation if still connected */
if (ShowLocalizedMsgEx(MB_YESNO, NULL, _T("Exit OpenVPN"), IDS_NFO_ACTIVE_CONN_EXIT) == IDNO)
{
return;
}
break; /* show the above message box only once */
}

DestroyWindow(hwnd);
Expand Down
2 changes: 2 additions & 0 deletions main.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,12 @@
#define WM_OVPN_NOTIFY (WM_APP + 16)
#define WM_OVPN_EXIT (WM_APP + 17)
#define WM_OVPN_SILENT (WM_APP + 18)
#define WM_OVPN_RELEASE (WM_APP + 19)
#define WM_OVPN_IMPORT (WM_APP + 20)
#define WM_OVPN_RESCAN (WM_APP + 21)
#define WM_OVPN_ECHOMSG (WM_APP + 22)
#define WM_OVPN_STATE (WM_APP + 23)
#define WM_OVPN_DETACH (WM_APP + 24)

/* bool definitions */
#define bool int
Expand Down
6 changes: 5 additions & 1 deletion manage.c
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,12 @@ OnManagement(SOCKET sk, LPARAM lParam)
case FD_CONNECT:
if (WSAGETSELECTERROR(lParam))
{
if (time(NULL) < c->manage.timeout)
/* keep trying for connections with persistent daemons */
if (c->flags & FLAG_DAEMON_PERSISTENT
|| time(NULL) < c->manage.timeout)
{
connect(c->manage.sk, (SOCKADDR *)&c->manage.skaddr, sizeof(c->manage.skaddr));
}
else
{
/* Connection to MI timed out. */
Expand Down
1 change: 1 addition & 0 deletions openvpn-gui-res.h
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@
#define IDS_NFO_CONFIG_AUTH_PENDING 1256
#define IDS_ERR_ADD_USER_TO_ADMIN_GROUP 1257
#define IDS_NFO_BYTECOUNT 1258
#define IDS_NFO_STATE_ONHOLD 1259

/* Program Startup Related */
#define IDS_ERR_OPEN_DEBUG_FILE 1301
Expand Down
164 changes: 155 additions & 9 deletions openvpn.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ OnReady(connection_t *c, UNUSED char *msg)
ManagementCommand(c, "log on all", OnLogLine, combined);
ManagementCommand(c, "echo on all", OnEcho, combined);
ManagementCommand(c, "bytecount 5", NULL, regular);

/* ask for the current state, especially useful when the daemon was prestarted */
ManagementCommand(c, "state", OnStateChange, regular);
}


Expand All @@ -130,6 +133,19 @@ OnReady(connection_t *c, UNUSED char *msg)
void
OnHold(connection_t *c, UNUSED char *msg)
{
EnableWindow(GetDlgItem(c->hwndStatus, ID_RESTART), TRUE);
if ((c->flags & FLAG_DAEMON_PERSISTENT) && (c->state == disconnecting))
{
/* retain the hold state if we are here while disconnecting */
c->state = onhold;
SetMenuStatus(c, onhold);
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_ONHOLD));
SetStatusWinIcon(c->hwndStatus, ID_ICO_DISCONNECTED);
EnableWindow(GetDlgItem(c->hwndStatus, ID_DISCONNECT), FALSE);
CheckAndSetTrayIcon();
return;
}
EnableWindow(GetDlgItem(c->hwndStatus, ID_DISCONNECT), TRUE);
ManagementCommand(c, "hold off", NULL, regular);
ManagementCommand(c, "hold release", NULL, regular);
}
Expand Down Expand Up @@ -296,8 +312,11 @@ OnStateChange(connection_t *c, char *data)
LoadLocalizedStringBuf(ip_txt, _countof(ip_txt), IDS_NFO_ASSIGN_IP, ip);

/* Run Connect Script */
if (c->state == connecting || c->state == resuming)
if (!(c->flags & FLAG_DAEMON_PERSISTENT)
&& (c->state == connecting || c->state == resuming))
{
RunConnectScript(c, false);
}

/* Show connection tray balloon */
if ((c->state == connecting && o.show_balloon != 0)
Expand Down Expand Up @@ -1319,6 +1338,7 @@ OnStop(connection_t *c, UNUSED char *msg)
c->failed_auth_attempts = 0;
c->state = disconnected;
CheckAndSetTrayIcon();
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_DISCONNECTED));
SendMessage(c->hwndStatus, WM_CLOSE, 0, 0);
break;

Expand Down Expand Up @@ -1753,6 +1773,44 @@ OnNeedStr (connection_t *c, UNUSED char *msg)
WriteStatusLog (c, L"GUI> ", L"Error: Received NEED-STR message -- not implemented", false);
}

/* Parse the management port and password of a
* a running daemon -- useful when the daemon is externally
* started (persistent) and we need to use the cached
* management interface address parameters to connect to it.
*/
static BOOL
ParseManagementAddress(connection_t *c)
{
/* Not implemented */
return false;
}

/* Stop the connection -- this sets the daemon to exit if
* started by us, else instructs the daemon to disconnect and
* and wait.
*/
static void
DisconnectDaemon(connection_t *c)
{
if (c->flags & FLAG_DAEMON_PERSISTENT)
{
if (c->manage.connected)
{
ManagementCommand(c, "hold on", NULL, regular);
ManagementCommand(c, "signal SIGHUP", NULL, regular);
}
else
{
OnStop(c, NULL);
}
}
else
{
SetEvent(c->exit_event);
SetTimer(c->hwndStatus, IDT_STOP_TIMER, 15000, NULL);
}
}

/*
* Close open handles
*/
Expand Down Expand Up @@ -1889,7 +1947,6 @@ StatusDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
return TRUE;

case ID_RESTART:
c->state = reconnecting;
SetFocus(GetDlgItem(c->hwndStatus, ID_EDT_LOG));
RestartOpenVPN(c);
return TRUE;
Expand Down Expand Up @@ -1921,19 +1978,42 @@ StatusDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
PostQuitMessage(0);
break;

case WM_OVPN_RELEASE:
c = (connection_t *) GetProp(hwndDlg, cfgProp);
c->state = reconnecting;
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_RECONNECTING));
SetDlgItemTextW(c->hwndStatus, ID_TXT_IP, L"");
SetStatusWinIcon(c->hwndStatus, ID_ICO_CONNECTING);
OnHold(c, "");
break;

case WM_OVPN_STOP:
c = (connection_t *) GetProp(hwndDlg, cfgProp);
/* external messages can trigger when we are not ready -- check the state */
if (!IsWindowEnabled(GetDlgItem(c->hwndStatus, ID_DISCONNECT)))
if (!IsWindowEnabled(GetDlgItem(c->hwndStatus, ID_DISCONNECT))
|| c->state == onhold)
{
break;
}
c->state = disconnecting;
RunDisconnectScript(c, false);
if (!(c->flags & FLAG_DAEMON_PERSISTENT))
{
RunDisconnectScript(c, false);
}
EnableWindow(GetDlgItem(c->hwndStatus, ID_DISCONNECT), FALSE);
EnableWindow(GetDlgItem(c->hwndStatus, ID_RESTART), FALSE);
SetMenuStatus(c, disconnecting);
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_WAIT_TERM));
SetEvent(c->exit_event);
SetTimer(hwndDlg, IDT_STOP_TIMER, 15000, NULL);
DisconnectDaemon(c);
break;

case WM_OVPN_DETACH:
c = (connection_t *) GetProp(hwndDlg, cfgProp);
/* just stop the thread keeping openvpn.exe running */
c->state = disconnecting;
EnableWindow(GetDlgItem(c->hwndStatus, ID_DISCONNECT), FALSE);
EnableWindow(GetDlgItem(c->hwndStatus, ID_RESTART), FALSE);
OnStop(c, NULL);
break;

case WM_OVPN_SUSPEND:
Expand Down Expand Up @@ -1962,7 +2042,13 @@ StatusDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
c = (connection_t *) GetProp(hwndDlg, cfgProp);
/* external messages can trigger when we are not ready -- check the state */
if (IsWindowEnabled(GetDlgItem(c->hwndStatus, ID_RESTART)))
{
c->state = reconnecting;
ManagementCommand(c, "signal SIGHUP", NULL, regular);
SetDlgItemText(c->hwndStatus, ID_TXT_STATUS, LoadLocalizedString(IDS_NFO_STATE_RECONNECTING));
SetDlgItemTextW(c->hwndStatus, ID_TXT_IP, L"");
SetStatusWinIcon(c->hwndStatus, ID_ICO_CONNECTING);
}
if (!o.silent_connection)
{
SetForegroundWindow(c->hwndStatus);
Expand Down Expand Up @@ -2037,7 +2123,21 @@ ThreadOpenVPNStatus(void *p)
while (WM_QUIT != msg.message)
{
DWORD res;
if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
if (wait_event == NULL) /* for persistent connections there is no wait_event */
{
res = GetMessage(&msg, NULL, 0, 0);
if (res == (DWORD) -1) /* log the error and continue */
{
MsgToEventLog(EVENTLOG_WARNING_TYPE, L"GetMessage for <%ls> returned error (status=%lu)",
c->config_name, GetLastError());
continue;
}
else if (res == 0) /* WM_QUIT */
{
break;
}
}
else if (!PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if ((res = MsgWaitForMultipleObjectsEx (1, &wait_event, INFINITE, QS_ALLINPUT,
MWMO_ALERTABLE)) == WAIT_OBJECT_0)
Expand Down Expand Up @@ -2140,6 +2240,17 @@ static char* PrepareStartJsonRequest(connection_t *c, wchar_t *exit_event_name)
}
#endif

/* If state is on hold -- release */
void
ReleaseOpenVPN(connection_t *c)
{
if (c->state != onhold)
{
return;
}
PostMessage(c->hwndStatus, WM_OVPN_RELEASE, 0, 0);
}

/* Start a thread to monitor a connection and launch openvpn.exe if required */
BOOL
StartOpenVPN(connection_t *c)
Expand All @@ -2148,6 +2259,11 @@ StartOpenVPN(connection_t *c)

if (c->hwndStatus)
{
if (c->state == onhold)
{
ReleaseOpenVPN(c);
return true;
}
PrintDebug(L"Connection request when already started -- ignored");
/* the tread can hang around after disconnect if user has not dismissed any popups */
if (c->state == disconnected)
Expand Down Expand Up @@ -2175,8 +2291,16 @@ StartOpenVPN(connection_t *c)
return false;
}

if (c->flags & FLAG_DAEMON_PERSISTENT)
{
if (!ParseManagementAddress(c))
{
TerminateThread(hThread, 1);
return false;
}
}
/* Launch openvpn.exe using the service or directly */
if (!LaunchOpenVPN(c))
else if (!LaunchOpenVPN(c))
{
TerminateThread(hThread, 1);
return false;
Expand Down Expand Up @@ -2388,6 +2512,20 @@ LaunchOpenVPN(connection_t *c)
}


/* Close the status thread without disconnecting the tunnel.
* Meant to be used only on persistent connections which can
* stay connected without the GUI tending to it.
*/
void
DetachOpenVPN(connection_t *c)
{
/* currently supported only for persistent connections */
if (c->flags & FLAG_DAEMON_PERSISTENT)
{
PostMessage(c->hwndStatus, WM_OVPN_DETACH, 0, 0);
}
}

void
StopOpenVPN(connection_t *c)
{
Expand Down Expand Up @@ -2431,10 +2569,18 @@ SuspendOpenVPN(int config)
void
RestartOpenVPN(connection_t *c)
{
if (c->hwndStatus)
if (c->state == onhold)
{
ReleaseOpenVPN(c);
}
else if (c->hwndStatus)
{
PostMessage(c->hwndStatus, WM_OVPN_RESTART, 0, 0);
}
else /* Not started: treat this as a request to connect */
{
StartOpenVPN(c);
}
}

void
Expand Down
2 changes: 2 additions & 0 deletions openvpn.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@

BOOL StartOpenVPN(connection_t *);
void StopOpenVPN(connection_t *);
void DetachOpenVPN(connection_t *);
void SuspendOpenVPN(int config);
void RestartOpenVPN(connection_t *);
void ReleaseOpenVPN(connection_t *);
BOOL CheckVersion();
void SetStatusWinIcon(HWND hwndDlg, int IconID);

Expand Down
2 changes: 2 additions & 0 deletions options.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ typedef enum {
/* connection states */
typedef enum {
disconnected,
onhold,
connecting,
reconnecting,
connected,
Expand All @@ -87,6 +88,7 @@ typedef struct {
#define FLAG_SAVE_AUTH_PASS (1<<5)
#define FLAG_DISABLE_SAVE_PASS (1<<6)
#define FLAG_DISABLE_ECHO_MSG (1<<7)
#define FLAG_DAEMON_PERSISTENT (1<<8)

#define CONFIG_VIEW_AUTO (0)
#define CONFIG_VIEW_FLAT (1)
Expand Down
1 change: 1 addition & 0 deletions res/openvpn-gui-res-cs.rc
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ Prosím dokončete předchozí autorizační dialog."
IDS_ERR_CREATE_THREAD_STATUS "CreateThread k zobrazení stavu selhal."
IDS_NFO_STATE_WAIT_TERM "Aktuální stav: Čekání, než se OpenVPN ukončí…"
IDS_NFO_STATE_CONNECTED "Aktuální stav: Připojeno"
IDS_NFO_STATE_ONHOLD "Current State: On Hold (disconnected)"
IDS_NFO_NOW_CONNECTED "%ls je nyní připojeno."
IDS_NFO_ASSIGN_IP "Přiřazená IP: %ls"
IDS_ERR_CERT_EXPIRED "Spojení nelze navázat, protože certifikát expiroval, nebo není správně nastaven systémový čas."
Expand Down
Loading

0 comments on commit 428ee29

Please sign in to comment.