@@ -140,63 +140,9 @@ func StartTunnel(
140140 if ! started .CompareAndSwap (false , true ) {
141141 return nil , errMultipleStart
142142 }
143-
144- config , err := psiphon .LoadConfig (configJSON )
145- if err != nil {
146- return nil , errors .TraceMsg (err , "failed to load config file" )
147- }
148-
149- // Use params.DataRootDirectory to set related config values.
150- if params .DataRootDirectory != nil {
151- config .DataRootDirectory = * params .DataRootDirectory
152-
153- // Migrate old fields
154- config .MigrateDataStoreDirectory = * params .DataRootDirectory
155- config .MigrateObfuscatedServerListDownloadDirectory = * params .DataRootDirectory
156- config .MigrateRemoteServerListDownloadFilename = filepath .Join (* params .DataRootDirectory , "server_list_compressed" )
157- }
158-
159- if params .NetworkID != nil {
160- config .NetworkID = * params .NetworkID
161- }
162-
163- if params .ClientPlatform != nil {
164- config .ClientPlatform = * params .ClientPlatform
165- } // else use the value in config
166-
167- if params .EstablishTunnelTimeoutSeconds != nil {
168- config .EstablishTunnelTimeoutSeconds = params .EstablishTunnelTimeoutSeconds
169- } // else use the value in config
170-
171- if config .UseNoticeFiles == nil && config .EmitDiagnosticNotices && params .EmitDiagnosticNoticesToFiles {
172- config .UseNoticeFiles = & psiphon.UseNoticeFiles {
173- RotatingFileSize : 0 ,
174- RotatingSyncFrequency : 0 ,
175- }
176- } // else use the value in the config
177-
178- if params .DisableLocalSocksProxy != nil {
179- config .DisableLocalSocksProxy = * params .DisableLocalSocksProxy
180- } // else use the value in the config
181-
182- if params .DisableLocalHTTPProxy != nil {
183- config .DisableLocalHTTPProxy = * params .DisableLocalHTTPProxy
184- } // else use the value in the config
185-
186- // config.Commit must be called before calling config.SetParameters
187- // or attempting to connect.
188- err = config .Commit (true )
189- if err != nil {
190- return nil , errors .TraceMsg (err , "config.Commit failed" )
191- }
192-
193- // If supplied, apply the parameters delta
194- if len (paramsDelta ) > 0 {
195- err = config .SetParameters ("" , false , paramsDelta )
196- if err != nil {
197- return nil , errors .TraceMsg (err , fmt .Sprintf ("SetParameters failed for delta: %v" , paramsDelta ))
198- }
199- }
143+ // There _must_ not be an early return between here and where tunnel.stop is deferred,
144+ // otherwise `started` will not get set back to false and we will be unable to call
145+ // StartTunnel again.
200146
201147 // Will be closed when the tunnel has successfully connected
202148 connectedSignal := make (chan struct {})
@@ -206,7 +152,8 @@ func StartTunnel(
206152 // Create the tunnel object
207153 tunnel := new (PsiphonTunnel )
208154
209- // Set up notice handling
155+ // Set up notice handling. It is important to do this before config operations, as
156+ // otherwise they will write notices to stderr.
210157 psiphon .SetNoticeWriter (psiphon .NewNoticeReceiver (
211158 func (notice []byte ) {
212159 var event NoticeEvent
@@ -247,11 +194,6 @@ func StartTunnel(
247194 }
248195 }))
249196
250- err = psiphon .OpenDataStore (config )
251- if err != nil {
252- return nil , errors .TraceMsg (err , "failed to open data store" )
253- }
254-
255197 // Create a cancelable context that will be used for stopping the tunnel
256198 tunnelCtx , cancelTunnelCtx := context .WithCancel (ctx )
257199
@@ -265,15 +207,82 @@ func StartTunnel(
265207 cancelTunnelCtx ()
266208 tunnel .embeddedServerListWaitGroup .Wait ()
267209 tunnel .controllerWaitGroup .Wait ()
210+ // This is safe to call even if the data store hasn't been opened
268211 psiphon .CloseDataStore ()
269212 started .Store (false )
213+ // Clear our notice receiver, as it is no longer needed and we should let it be
214+ // garbage-collected.
215+ psiphon .SetNoticeWriter (io .Discard )
270216 }
271217
272218 defer func () {
273219 if retErr != nil {
274220 tunnel .stop ()
275221 }
276222 }()
223+ // We have now set up our on-error cleanup and it is safe to have early error returns.
224+
225+ config , err := psiphon .LoadConfig (configJSON )
226+ if err != nil {
227+ return nil , errors .TraceMsg (err , "failed to load config file" )
228+ }
229+
230+ // Use params.DataRootDirectory to set related config values.
231+ if params .DataRootDirectory != nil {
232+ config .DataRootDirectory = * params .DataRootDirectory
233+
234+ // Migrate old fields
235+ config .MigrateDataStoreDirectory = * params .DataRootDirectory
236+ config .MigrateObfuscatedServerListDownloadDirectory = * params .DataRootDirectory
237+ config .MigrateRemoteServerListDownloadFilename = filepath .Join (* params .DataRootDirectory , "server_list_compressed" )
238+ }
239+
240+ if params .NetworkID != nil {
241+ config .NetworkID = * params .NetworkID
242+ }
243+
244+ if params .ClientPlatform != nil {
245+ config .ClientPlatform = * params .ClientPlatform
246+ } // else use the value in config
247+
248+ if params .EstablishTunnelTimeoutSeconds != nil {
249+ config .EstablishTunnelTimeoutSeconds = params .EstablishTunnelTimeoutSeconds
250+ } // else use the value in config
251+
252+ if config .UseNoticeFiles == nil && config .EmitDiagnosticNotices && params .EmitDiagnosticNoticesToFiles {
253+ config .UseNoticeFiles = & psiphon.UseNoticeFiles {
254+ RotatingFileSize : 0 ,
255+ RotatingSyncFrequency : 0 ,
256+ }
257+ } // else use the value in the config
258+
259+ if params .DisableLocalSocksProxy != nil {
260+ config .DisableLocalSocksProxy = * params .DisableLocalSocksProxy
261+ } // else use the value in the config
262+
263+ if params .DisableLocalHTTPProxy != nil {
264+ config .DisableLocalHTTPProxy = * params .DisableLocalHTTPProxy
265+ } // else use the value in the config
266+
267+ // config.Commit must be called before calling config.SetParameters
268+ // or attempting to connect.
269+ err = config .Commit (true )
270+ if err != nil {
271+ return nil , errors .TraceMsg (err , "config.Commit failed" )
272+ }
273+
274+ // If supplied, apply the parameters delta
275+ if len (paramsDelta ) > 0 {
276+ err = config .SetParameters ("" , false , paramsDelta )
277+ if err != nil {
278+ return nil , errors .TraceMsg (err , fmt .Sprintf ("SetParameters failed for delta: %v" , paramsDelta ))
279+ }
280+ }
281+
282+ err = psiphon .OpenDataStore (config )
283+ if err != nil {
284+ return nil , errors .TraceMsg (err , "failed to open data store" )
285+ }
277286
278287 // If specified, the embedded server list is loaded and stored. When there
279288 // are no server candidates at all, we wait for this import to complete
@@ -374,10 +383,6 @@ func (tunnel *PsiphonTunnel) Stop() {
374383 tunnel .stop ()
375384 tunnel .stop = nil
376385 tunnel .controllerDial = nil
377-
378- // Clear our notice receiver, as it is no longer needed and we should let it be
379- // garbage-collected.
380- psiphon .SetNoticeWriter (io .Discard )
381386}
382387
383388// Dial connects to the specified address through the Psiphon tunnel.
0 commit comments