github.com/psiphon-labs/psiphon-tunnel-core@v2.0.28+incompatible/MobileLibrary/psi/psi.go (about) 1 /* 2 * Copyright (c) 2015, Psiphon Inc. 3 * All rights reserved. 4 * 5 * This program is free software: you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation, either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package psi 21 22 // This package is a shim between Java/Obj-C and the "psiphon" package. Due to limitations 23 // on what Go types may be exposed (http://godoc.org/golang.org/x/mobile/cmd/gobind), 24 // a psiphon.Controller cannot be directly used by Java. This shim exposes a trivial 25 // Start/Stop interface on top of a single Controller instance. 26 27 import ( 28 "context" 29 "encoding/json" 30 "fmt" 31 "os" 32 "path/filepath" 33 "strings" 34 "sync" 35 36 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon" 37 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common" 38 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/buildinfo" 39 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/tun" 40 ) 41 42 type PsiphonProviderNoticeHandler interface { 43 Notice(noticeJSON string) 44 } 45 46 type PsiphonProviderNetwork interface { 47 HasNetworkConnectivity() int 48 GetNetworkID() string 49 IPv6Synthesize(IPv4Addr string) string 50 HasIPv6Route() int 51 } 52 53 type PsiphonProvider interface { 54 PsiphonProviderNoticeHandler 55 PsiphonProviderNetwork 56 BindToDevice(fileDescriptor int) (string, error) 57 58 // TODO: move GetDNSServersAsString to PsiphonProviderNetwork to 59 // facilitate custom tunnel-core resolver support in SendFeedback. 60 61 // GetDNSServersAsString must return a comma-delimited list of DNS server 62 // addresses. A single string return value is used since gobind does not 63 // support string slice types. 64 GetDNSServersAsString() string 65 } 66 67 type PsiphonProviderFeedbackHandler interface { 68 SendFeedbackCompleted(err error) 69 } 70 71 func NoticeUserLog(message string) { 72 psiphon.NoticeUserLog(message) 73 } 74 75 // HomepageFilePath returns the path where homepage files will be paved. 76 // 77 // rootDataDirectoryPath is the configured data root directory. 78 // 79 // Note: homepage files will only be paved if UseNoticeFiles is set in the 80 // config passed to Start(). 81 func HomepageFilePath(rootDataDirectoryPath string) string { 82 return filepath.Join(rootDataDirectoryPath, psiphon.PsiphonDataDirectoryName, psiphon.HomepageFilename) 83 } 84 85 // NoticesFilePath returns the path where the notices file will be paved. 86 // 87 // rootDataDirectoryPath is the configured data root directory. 88 // 89 // Note: notices will only be paved if UseNoticeFiles is set in the config 90 // passed to Start(). 91 func NoticesFilePath(rootDataDirectoryPath string) string { 92 return filepath.Join(rootDataDirectoryPath, psiphon.PsiphonDataDirectoryName, psiphon.NoticesFilename) 93 } 94 95 // OldNoticesFilePath returns the path where the notices file is moved to when 96 // file rotation occurs. 97 // 98 // rootDataDirectoryPath is the configured data root directory. 99 // 100 // Note: notices will only be paved if UseNoticeFiles is set in the config 101 // passed to Start(). 102 func OldNoticesFilePath(rootDataDirectoryPath string) string { 103 return filepath.Join(rootDataDirectoryPath, psiphon.PsiphonDataDirectoryName, psiphon.OldNoticesFilename) 104 } 105 106 // UpgradeDownloadFilePath returns the path where the downloaded upgrade file 107 // will be paved. 108 // 109 // rootDataDirectoryPath is the configured data root directory. 110 // 111 // Note: upgrades will only be paved if UpgradeDownloadURLs is set in the config 112 // passed to Start() and there are upgrades available. 113 func UpgradeDownloadFilePath(rootDataDirectoryPath string) string { 114 return filepath.Join(rootDataDirectoryPath, psiphon.PsiphonDataDirectoryName, psiphon.UpgradeDownloadFilename) 115 } 116 117 var controllerMutex sync.Mutex 118 var embeddedServerListWaitGroup *sync.WaitGroup 119 var controller *psiphon.Controller 120 var controllerCtx context.Context 121 var stopController context.CancelFunc 122 var controllerWaitGroup *sync.WaitGroup 123 124 func Start( 125 configJson string, 126 embeddedServerEntryList string, 127 embeddedServerEntryListFilename string, 128 provider PsiphonProvider, 129 useDeviceBinder bool, 130 useIPv6Synthesizer bool, 131 useHasIPv6RouteGetter bool) error { 132 133 controllerMutex.Lock() 134 defer controllerMutex.Unlock() 135 136 if controller != nil { 137 return fmt.Errorf("already started") 138 } 139 140 // Clients may toggle Stop/Start immediately to apply new config settings 141 // such as EgressRegion or Authorizations. When this restart is within the 142 // same process and in a memory contrained environment, it is useful to 143 // force garbage collection here to reclaim memory used by the previous 144 // Controller. 145 psiphon.DoGarbageCollection() 146 147 // Wrap the provider in a layer that locks a mutex before calling a provider function. 148 // As the provider callbacks are Java/Obj-C via gomobile, they are cgo calls that 149 // can cause OS threads to be spawned. The mutex prevents many calling goroutines from 150 // causing unbounded numbers of OS threads to be spawned. 151 // TODO: replace the mutex with a semaphore, to allow a larger but still bounded concurrent 152 // number of calls to the provider? 153 wrappedProvider := newMutexPsiphonProvider(provider) 154 155 config, err := psiphon.LoadConfig([]byte(configJson)) 156 if err != nil { 157 return fmt.Errorf("error loading configuration file: %s", err) 158 } 159 160 // Set up callbacks. 161 162 config.NetworkConnectivityChecker = wrappedProvider 163 config.NetworkIDGetter = wrappedProvider 164 config.DNSServerGetter = wrappedProvider 165 166 if useDeviceBinder { 167 config.DeviceBinder = wrappedProvider 168 } 169 170 if useIPv6Synthesizer { 171 config.IPv6Synthesizer = wrappedProvider 172 } 173 174 if useHasIPv6RouteGetter { 175 config.HasIPv6RouteGetter = wrappedProvider 176 } 177 178 // All config fields should be set before calling Commit. 179 180 err = config.Commit(true) 181 if err != nil { 182 return fmt.Errorf("error committing configuration file: %s", err) 183 } 184 185 psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver( 186 func(notice []byte) { 187 wrappedProvider.Notice(string(notice)) 188 })) 189 190 // BuildInfo is a diagnostic notice, so emit only after config.Commit 191 // sets EmitDiagnosticNotices. 192 193 psiphon.NoticeBuildInfo() 194 195 err = psiphon.OpenDataStore(config) 196 if err != nil { 197 return fmt.Errorf("error initializing datastore: %s", err) 198 } 199 200 controllerCtx, stopController = context.WithCancel(context.Background()) 201 202 // If specified, the embedded server list is loaded and stored. When there 203 // are no server candidates at all, we wait for this import to complete 204 // before starting the Psiphon controller. Otherwise, we import while 205 // concurrently starting the controller to minimize delay before attempting 206 // to connect to existing candidate servers. 207 // 208 // If the import fails, an error notice is emitted, but the controller is 209 // still started: either existing candidate servers may suffice, or the 210 // remote server list fetch may obtain candidate servers. 211 // 212 // The import will be interrupted if it's still running when the controller 213 // is stopped. 214 embeddedServerListWaitGroup = new(sync.WaitGroup) 215 embeddedServerListWaitGroup.Add(1) 216 go func() { 217 defer embeddedServerListWaitGroup.Done() 218 219 err := psiphon.ImportEmbeddedServerEntries( 220 controllerCtx, 221 config, 222 embeddedServerEntryListFilename, 223 embeddedServerEntryList) 224 if err != nil { 225 psiphon.NoticeError("error importing embedded server entry list: %s", err) 226 return 227 } 228 }() 229 if !psiphon.HasServerEntries() { 230 psiphon.NoticeInfo("awaiting embedded server entry list import") 231 embeddedServerListWaitGroup.Wait() 232 } 233 234 controller, err = psiphon.NewController(config) 235 if err != nil { 236 stopController() 237 embeddedServerListWaitGroup.Wait() 238 psiphon.CloseDataStore() 239 return fmt.Errorf("error initializing controller: %s", err) 240 } 241 242 controllerWaitGroup = new(sync.WaitGroup) 243 controllerWaitGroup.Add(1) 244 go func() { 245 defer controllerWaitGroup.Done() 246 controller.Run(controllerCtx) 247 }() 248 249 return nil 250 } 251 252 func Stop() { 253 254 controllerMutex.Lock() 255 defer controllerMutex.Unlock() 256 257 if controller != nil { 258 stopController() 259 controllerWaitGroup.Wait() 260 embeddedServerListWaitGroup.Wait() 261 psiphon.CloseDataStore() 262 controller = nil 263 controllerCtx = nil 264 stopController = nil 265 controllerWaitGroup = nil 266 } 267 } 268 269 // ReconnectTunnel initiates a reconnect of the current tunnel, if one is 270 // running. 271 func ReconnectTunnel() { 272 273 controllerMutex.Lock() 274 defer controllerMutex.Unlock() 275 276 if controller != nil { 277 controller.TerminateNextActiveTunnel() 278 } 279 } 280 281 // SetDynamicConfig overrides the sponsor ID and authorizations fields set in 282 // the config passed to Start. SetDynamicConfig has no effect if no Controller 283 // is started. 284 // 285 // The input newAuthorizationsList is a space-delimited list of base64 286 // authorizations. This is a workaround for gobind type limitations. 287 func SetDynamicConfig(newSponsorID, newAuthorizationsList string) { 288 289 controllerMutex.Lock() 290 defer controllerMutex.Unlock() 291 292 if controller != nil { 293 294 var authorizations []string 295 if len(newAuthorizationsList) > 0 { 296 authorizations = strings.Split(newAuthorizationsList, " ") 297 } 298 299 controller.SetDynamicConfig( 300 newSponsorID, 301 authorizations) 302 } 303 } 304 305 // ExportExchangePayload creates a payload for client-to-client server 306 // connection info exchange. 307 // 308 // ExportExchangePayload will succeed only when Psiphon is running, between 309 // Start and Stop. 310 // 311 // The return value is a payload that may be exchanged with another client; 312 // when "", the export failed and a diagnostic has been logged. 313 func ExportExchangePayload() string { 314 315 controllerMutex.Lock() 316 defer controllerMutex.Unlock() 317 318 if controller == nil { 319 return "" 320 } 321 322 return controller.ExportExchangePayload() 323 } 324 325 // ImportExchangePayload imports a payload generated by ExportExchangePayload. 326 // 327 // If an import occurs when Psiphon is working to establsh a tunnel, the newly 328 // imported server entry is prioritized. 329 // 330 // The return value indicates a successful import. If the import failed, a a 331 // diagnostic notice has been logged. 332 func ImportExchangePayload(payload string) bool { 333 334 controllerMutex.Lock() 335 defer controllerMutex.Unlock() 336 337 if controller == nil { 338 return false 339 } 340 341 return controller.ImportExchangePayload(payload) 342 } 343 344 var sendFeedbackMutex sync.Mutex 345 var sendFeedbackCtx context.Context 346 var stopSendFeedback context.CancelFunc 347 var sendFeedbackWaitGroup *sync.WaitGroup 348 349 // StartSendFeedback encrypts the provided diagnostics and then attempts to 350 // upload the encrypted diagnostics to one of the feedback upload locations 351 // supplied by the provided config or tactics. 352 // 353 // Returns immediately after starting the operation in a goroutine. The 354 // operation has completed when SendFeedbackCompleted(error) is called on the 355 // provided PsiphonProviderFeedbackHandler; if error is non-nil, then the 356 // operation failed. 357 // 358 // Only one active upload is supported at a time. An ongoing upload will be 359 // cancelled if this function is called again before it completes. 360 // 361 // Warnings: 362 // - Should not be used with Start concurrently in the same process 363 // - An ongoing feedback upload started with StartSendFeedback should be 364 // stopped with StopSendFeedback before the process exists. This ensures that 365 // any underlying resources are cleaned up; failing to do so may result in 366 // data store corruption or other undefined behavior. 367 // - Start and StartSendFeedback both make an attempt to migrate persistent 368 // files from legacy locations in a one-time operation. If these functions 369 // are called in parallel, then there is a chance that the migration attempts 370 // could execute at the same time and result in non-fatal errors in one, or 371 // both, of the migration operations. 372 // - Calling StartSendFeedback or StopSendFeedback on the same call stack 373 // that the PsiphonProviderFeedbackHandler.SendFeedbackCompleted() callback 374 // is delivered on can cause a deadlock. I.E. the callback code must return 375 // so the wait group can complete and the lock acquired in StopSendFeedback 376 // can be released. 377 func StartSendFeedback( 378 configJson, 379 diagnosticsJson, 380 uploadPath string, 381 feedbackHandler PsiphonProviderFeedbackHandler, 382 networkInfoProvider PsiphonProviderNetwork, 383 noticeHandler PsiphonProviderNoticeHandler, 384 useIPv6Synthesizer bool, 385 useHasIPv6RouteGetter bool) error { 386 387 // Cancel any ongoing uploads. 388 StopSendFeedback() 389 390 sendFeedbackMutex.Lock() 391 defer sendFeedbackMutex.Unlock() 392 393 sendFeedbackCtx, stopSendFeedback = context.WithCancel(context.Background()) 394 395 // Unlike in Start, the provider is not wrapped in a newMutexPsiphonProvider 396 // or equivilent, as SendFeedback is not expected to be used in a memory 397 // constrained environment. 398 399 psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver( 400 func(notice []byte) { 401 noticeHandler.Notice(string(notice)) 402 })) 403 404 config, err := psiphon.LoadConfig([]byte(configJson)) 405 if err != nil { 406 return fmt.Errorf("error loading configuration file: %s", err) 407 } 408 409 // Set up callbacks. 410 411 config.NetworkConnectivityChecker = networkInfoProvider 412 config.NetworkIDGetter = networkInfoProvider 413 414 if useIPv6Synthesizer { 415 config.IPv6Synthesizer = networkInfoProvider 416 } 417 418 if useHasIPv6RouteGetter { 419 config.HasIPv6RouteGetter = networkInfoProvider 420 } 421 422 // Limitation: config.DNSServerGetter is not set up in the SendFeedback 423 // case, as we don't currently implement network path and system DNS 424 // server monitoring for SendFeedback in the platform code. To ensure we 425 // fallback to the system resolver and don't always use the custom 426 // resolver with alternate DNS servers, clear that config field (this may 427 // still be set via tactics). 428 config.DNSResolverAlternateServers = nil 429 430 // All config fields should be set before calling Commit. 431 432 err = config.Commit(true) 433 if err != nil { 434 return fmt.Errorf("error committing configuration file: %s", err) 435 } 436 437 sendFeedbackWaitGroup = new(sync.WaitGroup) 438 sendFeedbackWaitGroup.Add(1) 439 go func() { 440 defer sendFeedbackWaitGroup.Done() 441 err := psiphon.SendFeedback(sendFeedbackCtx, config, 442 diagnosticsJson, uploadPath) 443 feedbackHandler.SendFeedbackCompleted(err) 444 }() 445 446 return nil 447 } 448 449 // StopSendFeedback interrupts an in-progress feedback upload operation 450 // started with `StartSendFeedback`. 451 // 452 // Warning: should not be used with Start concurrently in the same process. 453 func StopSendFeedback() { 454 455 sendFeedbackMutex.Lock() 456 defer sendFeedbackMutex.Unlock() 457 458 if stopSendFeedback != nil { 459 stopSendFeedback() 460 sendFeedbackWaitGroup.Wait() 461 sendFeedbackCtx = nil 462 stopSendFeedback = nil 463 sendFeedbackWaitGroup = nil 464 // Allow the notice receiver to be deallocated. 465 psiphon.SetNoticeWriter(os.Stderr) 466 } 467 } 468 469 // Get build info from tunnel-core 470 func GetBuildInfo() string { 471 buildInfo, err := json.Marshal(buildinfo.GetBuildInfo()) 472 if err != nil { 473 return "" 474 } 475 return string(buildInfo) 476 } 477 478 func GetPacketTunnelMTU() int { 479 return tun.DEFAULT_MTU 480 } 481 482 func GetPacketTunnelDNSResolverIPv4Address() string { 483 return tun.GetTransparentDNSResolverIPv4Address().String() 484 } 485 486 func GetPacketTunnelDNSResolverIPv6Address() string { 487 return tun.GetTransparentDNSResolverIPv6Address().String() 488 } 489 490 // WriteRuntimeProfiles writes Go runtime profile information to a set of 491 // files in the specified output directory. See common.WriteRuntimeProfiles 492 // for more details. 493 // 494 // If called before Start, log notices will emit to stderr. 495 func WriteRuntimeProfiles(outputDirectory string, cpuSampleDurationSeconds, blockSampleDurationSeconds int) { 496 common.WriteRuntimeProfiles( 497 psiphon.NoticeCommonLogger(), 498 outputDirectory, 499 "", 500 cpuSampleDurationSeconds, 501 blockSampleDurationSeconds) 502 } 503 504 type mutexPsiphonProvider struct { 505 sync.Mutex 506 p PsiphonProvider 507 } 508 509 func newMutexPsiphonProvider(p PsiphonProvider) *mutexPsiphonProvider { 510 return &mutexPsiphonProvider{p: p} 511 } 512 513 func (p *mutexPsiphonProvider) Notice(noticeJSON string) { 514 p.Lock() 515 defer p.Unlock() 516 p.p.Notice(noticeJSON) 517 } 518 519 func (p *mutexPsiphonProvider) HasNetworkConnectivity() int { 520 p.Lock() 521 defer p.Unlock() 522 return p.p.HasNetworkConnectivity() 523 } 524 525 func (p *mutexPsiphonProvider) BindToDevice(fileDescriptor int) (string, error) { 526 p.Lock() 527 defer p.Unlock() 528 return p.p.BindToDevice(fileDescriptor) 529 } 530 531 func (p *mutexPsiphonProvider) IPv6Synthesize(IPv4Addr string) string { 532 p.Lock() 533 defer p.Unlock() 534 return p.p.IPv6Synthesize(IPv4Addr) 535 } 536 537 func (p *mutexPsiphonProvider) HasIPv6Route() int { 538 p.Lock() 539 defer p.Unlock() 540 return p.p.HasIPv6Route() 541 } 542 543 func (p *mutexPsiphonProvider) GetDNSServersAsString() string { 544 p.Lock() 545 defer p.Unlock() 546 return p.p.GetDNSServersAsString() 547 } 548 549 func (p *mutexPsiphonProvider) GetDNSServers() []string { 550 p.Lock() 551 defer p.Unlock() 552 s := p.p.GetDNSServersAsString() 553 if s == "" { 554 return []string{} 555 } 556 return strings.Split(s, ",") 557 } 558 559 func (p *mutexPsiphonProvider) GetNetworkID() string { 560 p.Lock() 561 defer p.Unlock() 562 return p.p.GetNetworkID() 563 }