1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
|
import os
from twisted.internet.task import LoopingCall
from twisted.internet import reactor, defer
from twisted.logger import Logger
from .process import VPNProcess
from .constants import IS_LINUX
POLL_TIME = 1
class VPNControl(object):
"""
This is the high-level object that the service knows about.
It exposes the start and terminate methods.
On start, it spawns a VPNProcess instance that will use a vpnlauncher
suited for the running platform and connect to the management interface
opened by the openvpn process, executing commands over that interface on
demand.
This class also has knowledge of the reactor, since it controlls the
pollers that write and read to the management interface.
"""
TERMINATE_MAXTRIES = 10
TERMINATE_WAIT = 1 # secs
RESTART_WAIT = 2 # secs
OPENVPN_VERB = "openvpn_verb"
log = Logger()
def __init__(self, remotes, vpnconfig,
providerconfig, socket_host, socket_port):
self._vpnproc = None
self._pollers = []
self._openvpn_verb = None
self._user_stopped = False
self._remotes = remotes
self._vpnconfig = vpnconfig
self._providerconfig = providerconfig
self._host = socket_host
self._port = socket_port
def start(self):
self.log.debug('VPN: start')
self._user_stopped = False
self._stop_pollers()
args = [self._vpnconfig, self._providerconfig, self._host,
self._port]
kwargs = {'openvpn_verb': 4, 'remotes': self._remotes,
'restartfun': self.restart}
vpnproc = VPNProcess(*args, **kwargs)
if vpnproc.get_openvpn_process():
self.log.info(
'Another vpn process is running. Will try to stop it.')
vpnproc.stop_if_already_running()
try:
vpnproc.preUp()
except Exception as e:
self.log.error('Error on vpn pre-up {0!r}'.format(e))
return False
try:
cmd = vpnproc.getCommand()
except Exception as e:
self.log.error(
'Error while getting vpn command... {0!r}'.format(e))
return False
env = os.environ
try:
runningproc = reactor.spawnProcess(vpnproc, cmd[0], cmd, env)
except Exception as e:
self.log.error(
'Error while spwanning vpn process... {0!r}'.format(e))
return False
vpnproc.pid = runningproc.pid
self._vpnproc = vpnproc
# add pollers for status and state
# this could be extended to a collection of
# generic watchers
poll_list = [
LoopingCall(vpnproc.pollStatus),
LoopingCall(vpnproc.pollState),
LoopingCall(vpnproc.pollLog)]
self._pollers.extend(poll_list)
self._start_pollers()
return True
@defer.inlineCallbacks
def restart(self):
yield self.stop(shutdown=False, restart=True)
reactor.callLater(
self.RESTART_WAIT, self.start)
def stop(self, shutdown=False, restart=False):
"""
Stops the openvpn subprocess.
Attempts to send a SIGTERM first, and after a timeout
it sends a SIGKILL.
:param shutdown: whether this is the final shutdown
:type shutdown: bool
:param restart: whether this stop is part of a hard restart.
:type restart: bool
"""
# We assume that the only valid stops are initiated
# by an user action, not hard restarts
self._user_stopped = not restart
if self._vpnproc is not None:
self._vpnproc.restarting = restart
self._stop_pollers()
try:
if self._vpnproc is not None:
self._vpnproc.preDown()
except Exception as e:
self.log.error('Error on vpn pre-down {0!r}'.format(e))
raise
d = defer.succeed(True)
if IS_LINUX:
# TODO factor this out to a linux-only launcher mechanism.
# First we try to be polite and send a SIGTERM...
if self._vpnproc is not None:
self._sentterm = True
self._vpnproc.terminate(shutdown=shutdown)
# we trigger a countdown to be unpolite
# if strictly needed.
d = defer.Deferred()
reactor.callLater(
self.TERMINATE_WAIT, self._kill_if_left_alive, d)
self._vpnproc.traffic_status = (0, 0)
return d
@property
def status(self):
if not self._vpnproc:
return {'status': 'off', 'error': None}
return self._vpnproc.status
@property
def traffic_status(self):
return self._vpnproc.traffic_status
def _killit(self):
"""
Sends a kill signal to the process.
"""
self._stop_pollers()
if self._vpnproc is None:
self.log.debug("There's no vpn process running to kill.")
else:
self._vpnproc.aborted = True
self._vpnproc.killProcess()
def _kill_if_left_alive(self, deferred, tries=0):
"""
Check if the process is still alive, and send a
SIGKILL after a timeout period.
:param tries: counter of tries, used in recursion
:type tries: int
"""
if tries < self.TERMINATE_MAXTRIES:
if self._vpnproc.transport.pid is None:
deferred.callback(True)
return
else:
self.log.debug('Process did not die, waiting...')
tries += 1
reactor.callLater(self.TERMINATE_WAIT,
self._kill_if_left_alive, deferred, tries)
return
# after running out of patience, we try a killProcess
self.log.debug('Process did not die. Sending a SIGKILL.')
try:
self._killit()
except OSError:
self.log.error('Could not kill process!')
deferred.callback(True)
def _start_pollers(self):
"""
Iterate through the registered observers
and start the looping call for them.
"""
for poller in self._pollers:
poller.start(POLL_TIME)
def _stop_pollers(self):
"""
Iterate through the registered observers
and stop the looping calls if they are running.
"""
for poller in self._pollers:
if poller.running:
poller.stop()
self._pollers = []
|