Просмотр исходного кода

Enable outer client to make Psiphon Server API requests
* Expose server API requesting to Controller caller.
* Also expose in Java/Go shim library for Android.
* Currently just supporting POST request with payload
but no response payload.

Rod Hynes 9 лет назад
Родитель
Сommit
65d4d169c6
5 измененных файлов с 101 добавлено и 0 удалено
  1. 21 0
      AndroidLibrary/psi/psi.go
  2. 6 0
      ConsoleClient/main.go
  3. 55 0
      psiphon/controller.go
  4. 8 0
      psiphon/notice.go
  5. 11 0
      psiphon/serverApi.go

+ 21 - 0
AndroidLibrary/psi/psi.go

@@ -39,6 +39,7 @@ type PsiphonProvider interface {
 	GetSecondaryDnsServer() string
 }
 
+var controllerMutex sync.Mutex
 var controller *psiphon.Controller
 var shutdownBroadcast chan struct{}
 var controllerWaitGroup *sync.WaitGroup
@@ -48,6 +49,9 @@ func Start(
 	provider PsiphonProvider,
 	useDeviceBinder bool) error {
 
+	controllerMutex.Lock()
+	defer controllerMutex.Unlock()
+
 	if controller != nil {
 		return fmt.Errorf("already started")
 	}
@@ -106,6 +110,10 @@ func Start(
 }
 
 func Stop() {
+
+	controllerMutex.Lock()
+	defer controllerMutex.Unlock()
+
 	if controller != nil {
 		close(shutdownBroadcast)
 		controllerWaitGroup.Wait()
@@ -114,3 +122,16 @@ func Stop() {
 		controllerWaitGroup = nil
 	}
 }
+
+// See description in Controller.MakeServerAPIRequest.
+func MakeServerAPIRequest(
+	requestID, requestName, requestPayloadJSON string, retryDelaySeconds int) {
+
+	controllerMutex.Lock()
+	defer controllerMutex.Unlock()
+
+	if controller != nil {
+		controller.MakeServerAPIRequest(
+			requestID, requestName, requestPayloadJSON, retryDelaySeconds)
+	}
+}

+ 6 - 0
ConsoleClient/main.go

@@ -27,6 +27,7 @@ import (
 	"os/signal"
 	"runtime/pprof"
 	"sync"
+	"time"
 
 	"github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon"
 )
@@ -183,6 +184,11 @@ func main() {
 		controllerStopSignal <- *new(struct{})
 	}()
 
+	// **TEMP**
+	time.Sleep(5 * time.Second)
+	controller.MakeServerAPIRequest("ID1", "feedback1", "", 1)
+	// **TEMP**
+
 	// Wait for an OS signal or a Run stop signal, then stop Psiphon and exit
 
 	systemStopSignal := make(chan os.Signal, 1)

+ 55 - 0
psiphon/controller.go

@@ -481,6 +481,61 @@ downloadLoop:
 	NoticeInfo("exiting upgrade downloader")
 }
 
+// MakeServerAPIRequest submits a caller-defined request to the
+// Psiphon API via a currently established tunnel. This is used
+// by higher-level client code to perform non-tunnel-core API requests.
+//
+// requestName and requestPayloadJSON define the API request. The "common"
+// API inputs are added to the request as query parameters. The request
+// is a POST with "application/json" encoding.
+//
+// requestID is a caller-selected unique ID used to identify response
+// Notices for this request. The request is sent in a background goroutine.
+// This function does not block. After the request completes, the requestID
+// is reported in a Notice.
+//
+// If the request does not complete successfully or there is no active tunnel,
+// the request will be retried. retryDelaySeconds specifies a pause period
+// between retries.
+//
+// Current limitations:
+// - Assumes HTTP status code 200 is expected; will retry on all other HTTP
+//   status codes
+// - GET requests unsupported
+// - response payloads unsupported
+//
+func (controller *Controller) MakeServerAPIRequest(
+	requestID, requestName, requestPayloadJSON string, retryDelaySeconds int) {
+
+	controller.runWaitGroup.Add(1)
+	go func() {
+		defer controller.runWaitGroup.Done()
+	loop:
+		for {
+
+			tunnel := controller.getNextActiveTunnel()
+			if tunnel != nil {
+				err := tunnel.serverContext.DoServerAPIRequest(
+					requestName, requestPayloadJSON)
+				if err == nil {
+					NoticeServerAPIRequestCompleted(requestID)
+					break loop
+				}
+				NoticeServerAPIRequestFailed(requestID, err)
+			}
+
+			timeout := time.After(time.Duration(retryDelaySeconds) * time.Second)
+			select {
+			case <-timeout:
+				// Attempt the request again
+
+			case <-controller.shutdownBroadcast:
+				break loop
+			}
+		}
+	}()
+}
+
 // runTunnels is the controller tunnel management main loop. It starts and stops
 // establishing tunnels based on the target tunnel pool size and the current size
 // of the pool. Tunnels are established asynchronously using worker goroutines.

+ 8 - 0
psiphon/notice.go

@@ -337,6 +337,14 @@ func NoticeRemoteServerListDownloaded(filename string) {
 	outputNotice("RemoteServerListDownloaded", false, false, "filename", filename)
 }
 
+func NoticeServerAPIRequestCompleted(requestID string) {
+	outputNotice("NoticeServerAPIRequestCompleted", false, false, "requestID", requestID)
+}
+
+func NoticeServerAPIRequestFailed(requestID string, err error) {
+	outputNotice("NoticeServerAPIRequestFailed", false, false, "requestID", requestID, "error", err.Error())
+}
+
 type repetitiveNoticeState struct {
 	message string
 	repeats int

+ 11 - 0
psiphon/serverApi.go

@@ -553,6 +553,17 @@ func RecordTunnelStats(
 	return StoreTunnelStats(tunnelStatsJson)
 }
 
+// DoServerAPIRequest performs a caller-defined Server API request.
+// See description in Controller.MakeServerAPIRequest.
+func (serverContext *ServerContext) DoServerAPIRequest(
+	requestName, requestPayload string) error {
+
+	return serverContext.doPostRequest(
+		buildRequestUrl(serverContext.baseRequestUrl, requestName),
+		"application/json",
+		bytes.NewReader([]byte(requestPayload)))
+}
+
 // doGetRequest makes a tunneled HTTPS request and returns the response body.
 func (serverContext *ServerContext) doGetRequest(
 	requestUrl string) (responseBody []byte, err error) {