github.com/bepass-org/wireguard-go@v1.0.4-rc2.0.20240304192354-ebce6572bc24/psiphon/p.go (about) 1 package psiphon 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "net" 9 "path/filepath" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon" 15 "github.com/refraction-networking/conjure/pkg/station/log" 16 ) 17 18 // Parameters provide an easier way to modify the tunnel config at runtime. 19 type Parameters struct { 20 // Used as the directory for the datastore, remote server list, and obfuscasted 21 // server list. 22 // Empty string means the default will be used (current working directory). 23 // nil means the values in the config file will be used. 24 // Optional, but strongly recommended. 25 DataRootDirectory *string 26 27 // Overrides config.ClientPlatform. See config.go for details. 28 // nil means the value in the config file will be used. 29 // Optional, but strongly recommended. 30 ClientPlatform *string 31 32 // Overrides config.NetworkID. For details see: 33 // https://godoc.org/github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon#NetworkIDGetter 34 // nil means the value in the config file will be used. (If not set in the config, 35 // an error will result.) 36 // Empty string will produce an error. 37 // Optional, but strongly recommended. 38 NetworkID *string 39 40 // Overrides config.EstablishTunnelTimeoutSeconds. See config.go for details. 41 // nil means the EstablishTunnelTimeoutSeconds value in the config file will be used. 42 // If there's no such value in the config file, the default will be used. 43 // Zero means there will be no timeout. 44 // Optional. 45 EstablishTunnelTimeoutSeconds *int 46 47 // EmitDiagnosticNoticesToFile indicates whether to use the rotating log file 48 // facility to record diagnostic notices instead of sending diagnostic 49 // notices to noticeReceiver. Has no effect unless the tunnel 50 // config.EmitDiagnosticNotices flag is set. 51 EmitDiagnosticNoticesToFiles bool 52 } 53 54 // Tunnel is the tunnel object. It can be used for stopping the tunnel and 55 // retrieving proxy ports. 56 type Tunnel struct { 57 embeddedServerListWaitGroup sync.WaitGroup 58 controllerWaitGroup sync.WaitGroup 59 stopController context.CancelFunc 60 61 // The port on which the HTTP proxy is running 62 HTTPProxyPort int 63 // The port on which the SOCKS proxy is running 64 SOCKSProxyPort int 65 } 66 67 // ParametersDelta allows for fine-grained modification of parameters.Parameters. 68 // NOTE: Ordinary users of this library should never need this. 69 type ParametersDelta map[string]interface{} 70 71 // NoticeEvent represents the notices emitted by tunnel core. It will be passed to 72 // noticeReceiver, if supplied. 73 // NOTE: Ordinary users of this library should never need this. 74 type NoticeEvent struct { 75 Data map[string]interface{} `json:"data"` 76 Type string `json:"noticeType"` 77 Timestamp string `json:"timestamp"` 78 } 79 80 // ErrTimeout is returned when the tunnel establishment attempt fails due to timeout 81 var ErrTimeout = errors.New("clientlib: tunnel establishment timeout") 82 83 // StartTunnel establishes a Psiphon tunnel. It returns an error if the establishment 84 // was not successful. If the returned error is nil, the returned tunnel can be used 85 // to find out the proxy ports and subsequently stop the tunnel. 86 // 87 // ctx may be cancelable, if the caller wants to be able to interrupt the establishment 88 // attempt, or context.Background(). 89 // 90 // configJSON will be passed to psiphon.LoadConfig to configure the tunnel. Required. 91 // 92 // embeddedServerEntryList is the encoded embedded server entry list. It is optional. 93 // 94 // params are config values that typically need to be overridden at runtime. 95 // 96 // paramsDelta contains changes that will be applied to the Parameters. 97 // NOTE: Ordinary users of this library should never need this and should pass nil. 98 // 99 // noticeReceiver, if non-nil, will be called for each notice emitted by tunnel core. 100 // NOTE: Ordinary users of this library should never need this and should pass nil. 101 func StartTunnel( 102 ctx context.Context, 103 configJSON []byte, 104 embeddedServerEntryList string, 105 params Parameters, 106 paramsDelta ParametersDelta, 107 noticeReceiver func(NoticeEvent)) (retTunnel *Tunnel, retErr error) { 108 109 config, err := psiphon.LoadConfig(configJSON) 110 if err != nil { 111 return nil, errors.New("failed to load config file") 112 } 113 114 // Use params.DataRootDirectory to set related config values. 115 if params.DataRootDirectory != nil { 116 config.DataRootDirectory = *params.DataRootDirectory 117 118 // Migrate old fields 119 config.MigrateDataStoreDirectory = *params.DataRootDirectory 120 config.MigrateObfuscatedServerListDownloadDirectory = *params.DataRootDirectory 121 config.MigrateRemoteServerListDownloadFilename = filepath.Join(*params.DataRootDirectory, "server_list_compressed") 122 } 123 124 if params.NetworkID != nil { 125 config.NetworkID = *params.NetworkID 126 } 127 128 if params.ClientPlatform != nil { 129 config.ClientPlatform = *params.ClientPlatform 130 } // else use the value in config 131 132 if params.EstablishTunnelTimeoutSeconds != nil { 133 config.EstablishTunnelTimeoutSeconds = params.EstablishTunnelTimeoutSeconds 134 } // else use the value in config 135 136 if config.UseNoticeFiles == nil && config.EmitDiagnosticNotices && params.EmitDiagnosticNoticesToFiles { 137 config.UseNoticeFiles = &psiphon.UseNoticeFiles{ 138 RotatingFileSize: 0, 139 RotatingSyncFrequency: 0, 140 } 141 } // else use the value in the config 142 143 // config.Commit must be called before calling config.SetParameters 144 // or attempting to connect. 145 err = config.Commit(true) 146 if err != nil { 147 return nil, errors.New("config.Commit failed") 148 } 149 150 // If supplied, apply the parameters delta 151 if len(paramsDelta) > 0 { 152 err = config.SetParameters("", false, paramsDelta) 153 if err != nil { 154 return nil, fmt.Errorf("set parameters failed for delta %v : %w", paramsDelta, err) 155 } 156 } 157 158 // Will receive a value when the tunnel has successfully connected. 159 connected := make(chan struct{}, 1) 160 // Will receive a value if an error occurs during the connection sequence. 161 errored := make(chan error, 1) 162 163 // Create the tunnel object 164 tunnel := new(Tunnel) 165 166 // Set up notice handling 167 psiphon.SetNoticeWriter(psiphon.NewNoticeReceiver( 168 func(notice []byte) { 169 var event NoticeEvent 170 err := json.Unmarshal(notice, &event) 171 if err != nil { 172 // This is unexpected and probably indicates something fatal has occurred. 173 // We'll interpret it as a connection error and abort. 174 err = errors.New("failed to unmarshal notice JSON") 175 select { 176 case errored <- err: 177 default: 178 } 179 return 180 } 181 182 if event.Type == "ListeningHttpProxyPort" { 183 port := event.Data["port"].(float64) 184 tunnel.HTTPProxyPort = int(port) 185 } else if event.Type == "ListeningSocksProxyPort" { 186 port := event.Data["port"].(float64) 187 tunnel.SOCKSProxyPort = int(port) 188 } else if event.Type == "EstablishTunnelTimeout" { 189 select { 190 case errored <- ErrTimeout: 191 default: 192 } 193 } else if event.Type == "Tunnels" { 194 count := event.Data["count"].(float64) 195 if count > 0 { 196 select { 197 case connected <- struct{}{}: 198 default: 199 } 200 } 201 } 202 203 // Some users of this package may need to add special processing of notices. 204 // If the caller has requested it, we'll pass on the notices. 205 if noticeReceiver != nil { 206 noticeReceiver(event) 207 } 208 })) 209 210 err = psiphon.OpenDataStore(config) 211 if err != nil { 212 return nil, errors.New("failed to open data store") 213 } 214 // Make sure we close the datastore in case of error 215 defer func() { 216 if retErr != nil { 217 tunnel.controllerWaitGroup.Wait() 218 tunnel.embeddedServerListWaitGroup.Wait() 219 psiphon.CloseDataStore() 220 } 221 }() 222 223 // Create a cancelable context that will be used for stopping the tunnel 224 var controllerCtx context.Context 225 controllerCtx, tunnel.stopController = context.WithCancel(ctx) 226 227 // If specified, the embedded server list is loaded and stored. When there 228 // are no server candidates at all, we wait for this import to complete 229 // before starting the Psiphon controller. Otherwise, we import while 230 // concurrently starting the controller to minimize delay before attempting 231 // to connect to existing candidate servers. 232 // 233 // If the import fails, an error notice is emitted, but the controller is 234 // still started: either existing candidate servers may suffice, or the 235 // remote server list fetch may obtain candidate servers. 236 // 237 // The import will be interrupted if it's still running when the controller 238 // is stopped. 239 tunnel.embeddedServerListWaitGroup.Add(1) 240 go func() { 241 defer tunnel.embeddedServerListWaitGroup.Done() 242 243 err := psiphon.ImportEmbeddedServerEntries( 244 controllerCtx, 245 config, 246 "", 247 embeddedServerEntryList) 248 if err != nil { 249 psiphon.NoticeError("error importing embedded server entry list: %s", err) 250 return 251 } 252 }() 253 if !psiphon.HasServerEntries() { 254 psiphon.NoticeInfo("awaiting embedded server entry list import") 255 tunnel.embeddedServerListWaitGroup.Wait() 256 } 257 258 // Create the Psiphon controller 259 controller, err := psiphon.NewController(config) 260 if err != nil { 261 tunnel.stopController() 262 tunnel.embeddedServerListWaitGroup.Wait() 263 return nil, errors.New("psiphon.NewController failed") 264 } 265 266 // Begin tunnel connection 267 tunnel.controllerWaitGroup.Add(1) 268 go func() { 269 defer tunnel.controllerWaitGroup.Done() 270 271 // Start the tunnel. Only returns on error (or internal timeout). 272 controller.Run(controllerCtx) 273 274 // controller.Run does not exit until the goroutine that posts 275 // EstablishTunnelTimeout has terminated; so, if there was a 276 // EstablishTunnelTimeout event, ErrTimeout is guaranteed to be sent to 277 // errord before this next error and will be the StartTunnel return value. 278 279 var err error 280 switch ctx.Err() { 281 case context.DeadlineExceeded: 282 err = ErrTimeout 283 case context.Canceled: 284 err = errors.New("StartTunnel canceled") 285 default: 286 err = errors.New("controller.Run exited unexpectedly") 287 } 288 select { 289 case errored <- err: 290 default: 291 } 292 }() 293 294 // Wait for an active tunnel or error 295 select { 296 case <-connected: 297 return tunnel, nil 298 case err := <-errored: 299 tunnel.Stop() 300 if err != ErrTimeout { 301 err = errors.New("tunnel start produced error") 302 } 303 return nil, err 304 } 305 } 306 307 // Stop stops/disconnects/shuts down the tunnel. It is safe to call when not connected. 308 // Not safe to call concurrently with Start. 309 func (tunnel *Tunnel) Stop() { 310 if tunnel.stopController == nil { 311 return 312 } 313 tunnel.stopController() 314 tunnel.controllerWaitGroup.Wait() 315 tunnel.embeddedServerListWaitGroup.Wait() 316 psiphon.CloseDataStore() 317 } 318 319 func RunPsiphon(wgBind, localSocksPort, country string, ctx context.Context) error { 320 // Embedded configuration 321 host, port, err := net.SplitHostPort(localSocksPort) 322 if err != nil { 323 return err 324 } 325 if strings.HasPrefix(host, "127.0.0") { 326 host = "" 327 } else { 328 host = "any" 329 } 330 configJSON := `{ 331 "EgressRegion": "` + country + `", 332 "ListenInterface": "` + host + `", 333 "LocalSocksProxyPort": ` + port + `, 334 "UpstreamProxyURL": "socks5://` + wgBind + `", 335 "DisableLocalHTTPProxy": true, 336 "PropagationChannelId":"FFFFFFFFFFFFFFFF", 337 "RemoteServerListDownloadFilename":"remote_server_list", 338 "RemoteServerListSignaturePublicKey":"MIICIDANBgkqhkiG9w0BAQEFAAOCAg0AMIICCAKCAgEAt7Ls+/39r+T6zNW7GiVpJfzq/xvL9SBH5rIFnk0RXYEYavax3WS6HOD35eTAqn8AniOwiH+DOkvgSKF2caqk/y1dfq47Pdymtwzp9ikpB1C5OfAysXzBiwVJlCdajBKvBZDerV1cMvRzCKvKwRmvDmHgphQQ7WfXIGbRbmmk6opMBh3roE42KcotLFtqp0RRwLtcBRNtCdsrVsjiI1Lqz/lH+T61sGjSjQ3CHMuZYSQJZo/KrvzgQXpkaCTdbObxHqb6/+i1qaVOfEsvjoiyzTxJADvSytVtcTjijhPEV6XskJVHE1Zgl+7rATr/pDQkw6DPCNBS1+Y6fy7GstZALQXwEDN/qhQI9kWkHijT8ns+i1vGg00Mk/6J75arLhqcodWsdeG/M/moWgqQAnlZAGVtJI1OgeF5fsPpXu4kctOfuZlGjVZXQNW34aOzm8r8S0eVZitPlbhcPiR4gT/aSMz/wd8lZlzZYsje/Jr8u/YtlwjjreZrGRmG8KMOzukV3lLmMppXFMvl4bxv6YFEmIuTsOhbLTwFgh7KYNjodLj/LsqRVfwz31PgWQFTEPICV7GCvgVlPRxnofqKSjgTWI4mxDhBpVcATvaoBl1L/6WLbFvBsoAUBItWwctO2xalKxF5szhGm8lccoc5MZr8kfE0uxMgsxz4er68iCID+rsCAQM=", 339 "RemoteServerListUrl":"https://s3.amazonaws.com//psiphon/web/mjr4-p23r-puwl/server_list_compressed", 340 "SponsorId":"FFFFFFFFFFFFFFFF", 341 "UseIndistinguishableTLS":true, 342 "AllowDefaultDNSResolverWithBindToDevice":true 343 }` 344 345 dir := "." 346 ClientPlatform := "Android_4.0.4_com.example.exampleClientLibraryApp" 347 network := "test" 348 timeout := 60 349 350 p := Parameters{ 351 DataRootDirectory: &dir, 352 ClientPlatform: &ClientPlatform, 353 NetworkID: &network, 354 EstablishTunnelTimeoutSeconds: &timeout, 355 EmitDiagnosticNoticesToFiles: false, 356 } 357 358 log.Println("Handshaking, Please Wait...") 359 360 var tunnel *Tunnel 361 startTime := time.Now() 362 363 internalCtx := context.Background() 364 365 timeoutTimer := time.NewTimer(2 * time.Minute) 366 defer timeoutTimer.Stop() 367 368 for { 369 select { 370 case <-ctx.Done(): 371 internalCtx.Done() 372 return fmt.Errorf("psiphon handshake operation canceled by user") 373 case <-timeoutTimer.C: 374 // Handle the internal timeout 375 internalCtx.Done() 376 return fmt.Errorf("psiphon handshake maximum time exceeded") 377 default: 378 tunnel, err = StartTunnel(internalCtx, []byte(configJSON), "", p, nil, nil) 379 if err == nil { 380 log.Println("Psiphon started successfully on port", tunnel.SOCKSProxyPort, "handshake operation took", int64(time.Since(startTime)/time.Millisecond), "milliseconds") 381 return nil 382 } 383 log.Error("Unable to start psiphon", err, "reconnecting...") 384 time.Sleep(1 * time.Second) 385 } 386 } 387 }