diff --git a/client/handler.go b/client/handler.go index 912b69b35..f783474f4 100644 --- a/client/handler.go +++ b/client/handler.go @@ -13,9 +13,7 @@ import ( "github.com/getlantern/proxy/v3/filters" "github.com/getlantern/flashlight/v7/chained" - "github.com/getlantern/flashlight/v7/common" "github.com/getlantern/flashlight/v7/ops" - "github.com/getlantern/flashlight/v7/pro" ) func (client *Client) handle(conn net.Conn) error { @@ -62,11 +60,6 @@ func (client *Client) filter(cs *filters.ConnectionState, req *http.Request, nex req.URL.Scheme = "http" req.URL.Host = req.Host - if common.Platform == "android" && req.URL != nil && req.URL.Host == "localhost" && - strings.HasPrefix(req.URL.Path, "/pro/") { - return client.interceptProRequest(cs, req) - } - op, ok := client.opsMap.get(cs.Downstream()) if ok { op.UserAgent(req.Header.Get("User-Agent")).OriginFromRequest(req) @@ -154,24 +147,6 @@ func (client *Client) isHTTPProxyPort(r *http.Request) bool { return false } -// interceptProRequest specifically looks for and properly handles pro server -// requests (similar to desktop's APIHandler) -func (client *Client) interceptProRequest(cs *filters.ConnectionState, r *http.Request) (*http.Response, *filters.ConnectionState, error) { - log.Debugf("Intercepting request to pro server: %v", r.URL.Path) - r.URL.Path = r.URL.Path[4:] - pro.PrepareProRequest(r, client.user) - r.Header.Del("Origin") - resp, err := pro.GetHTTPClient().Do(r) - if err != nil { - log.Errorf("Error intercepting request to pro server: %v", err) - resp = &http.Response{ - StatusCode: http.StatusInternalServerError, - Close: true, - } - } - return filters.ShortCircuit(cs, r, resp) -} - func (client *Client) easyblock(cs *filters.ConnectionState, req *http.Request) (*http.Response, *filters.ConnectionState, error) { log.Debugf("Blocking %v on %v", req.URL, req.Host) client.statsTracker.IncAdsBlocked() diff --git a/go.mod b/go.mod index bbd64f08b..5e4f11a33 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/getlantern/flashlight/v7 -go 1.21 +go 1.22.0 replace github.com/elazarl/goproxy => github.com/getlantern/goproxy v0.0.0-20220805074304-4a43a9ed4ec6 @@ -30,8 +30,6 @@ require ( github.com/anacrolix/dht/v2 v2.20.0 github.com/blang/semver v3.5.1+incompatible github.com/dustin/go-humanize v1.0.1 - github.com/eycorsican/go-tun2socks v1.16.12-0.20201107203946-301549c435ff - github.com/fsnotify/fsnotify v1.6.0 github.com/getlantern/broflake v0.0.0-20231117182649-7d46643a6f87 github.com/getlantern/bufconn v0.0.0-20210901195825-fd7c0267b493 github.com/getlantern/cmux/v2 v2.0.0-20230301223233-dac79088a4c0 @@ -41,7 +39,7 @@ require ( github.com/getlantern/dnsgrab v0.0.0-20211216020425-5d5e155a01a8 github.com/getlantern/domains v0.0.0-20220311111720-94f59a903271 github.com/getlantern/ema v0.0.0-20190620044903-5943d28f40e4 - github.com/getlantern/errors v1.0.3 + github.com/getlantern/errors v1.0.5-0.20240410211607-f268a297d5d1 github.com/getlantern/event v0.0.0-20210901195647-a7e3145142e6 github.com/getlantern/eventual v1.0.0 github.com/getlantern/eventual/v2 v2.0.2 @@ -72,7 +70,6 @@ require ( github.com/getlantern/replica v0.14.3 github.com/getlantern/rot13 v0.0.0-20220822172233-370767b2f782 github.com/getlantern/rotator v0.0.0-20160829164113-013d4f8e36a2 - github.com/getlantern/safechannels v0.0.0-20201218194342-b4e5383e9627 github.com/getlantern/shortcut v0.0.0-20211026183428-bf59a137fdec github.com/getlantern/timezone v0.0.0-20210901200113-3f9de9d360c9 github.com/getlantern/tinywss v0.0.0-20211216020538-c10008a7d461 @@ -86,7 +83,6 @@ require ( github.com/getlantern/yaml v0.0.0-20190801163808-0c9bb1ebf426 github.com/getsentry/sentry-go v0.20.0 github.com/golang/protobuf v1.5.3 - github.com/google/gopacket v1.1.19 github.com/hashicorp/golang-lru v0.5.4 github.com/jaffee/commandeer v0.6.0 github.com/keighl/mandrill v0.0.0-20170605120353-1775dd4b3b41 @@ -113,6 +109,8 @@ require ( howett.net/plist v1.0.0 ) +require github.com/fsnotify/fsnotify v1.6.0 // indirect + require ( git.torproject.org/pluggable-transports/goptlib.git v1.2.0 // indirect github.com/dchest/siphash v1.2.3 // indirect diff --git a/go.sum b/go.sum index c00291963..92885beb3 100644 --- a/go.sum +++ b/go.sum @@ -242,6 +242,10 @@ github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFB github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A= github.com/getlantern/errors v1.0.3 h1:Ne4Ycj7NI1BtSyAfVeAT/DNoxz7/S2BUc3L2Ht1YSHE= github.com/getlantern/errors v1.0.3/go.mod h1:m8C7H1qmouvsGpwQqk/6NUpIVMpfzUPn608aBZDYV04= +github.com/getlantern/errors v1.0.5-0.20240410211410-efcb55d54ffb h1:I8oXS6xtVXtxq5RBpAkxV8tyrS6dq5akyBZsnkzozns= +github.com/getlantern/errors v1.0.5-0.20240410211410-efcb55d54ffb/go.mod h1:L1h+WK2Enz9MzoHsyGpdtQlwXe7U/ANbSM4rEbAl3N8= +github.com/getlantern/errors v1.0.5-0.20240410211607-f268a297d5d1 h1:06/WReVGjGazEKDQcT/OADWkhr/EQY3Q8TdtLCZfu5E= +github.com/getlantern/errors v1.0.5-0.20240410211607-f268a297d5d1/go.mod h1:L1h+WK2Enz9MzoHsyGpdtQlwXe7U/ANbSM4rEbAl3N8= github.com/getlantern/event v0.0.0-20210901195647-a7e3145142e6 h1:sjFsoQHJqzDiwgbOLHnG/zYIpN1Sbmv/7gk1ie/KkHg= github.com/getlantern/event v0.0.0-20210901195647-a7e3145142e6/go.mod h1:iToZ3dqm/iFxRHPHUHUrF1JZtg0e06ZSXD1BuiGoUaY= github.com/getlantern/eventual v0.0.0-20180125201821-84b02499361b/go.mod h1:O8T3zFEcY6+LRXFcVV4q8mEu2tDIixG8edC84DfswBc= @@ -266,8 +270,6 @@ github.com/getlantern/geolookup v0.0.0-20230327091034-aebe73c6eef4 h1:Ju9l1RretV github.com/getlantern/geolookup v0.0.0-20230327091034-aebe73c6eef4/go.mod h1:4UNvIsawdB8WclVxqYv46Oe1zzWJ8wMhUO+q6tUzATo= github.com/getlantern/go-socks5 v0.0.0-20171114193258-79d4dd3e2db5 h1:RBKofGGMt2k6eGBwX8mky9qunjL+KnAp9JdzXjiRkRw= github.com/getlantern/go-socks5 v0.0.0-20171114193258-79d4dd3e2db5/go.mod h1:kGHRXch95rnGLHjER/GhhFiHvfnqNz7KqWD9kGfATHY= -github.com/getlantern/go-tun2socks v1.16.12-0.20201218023150-b68f09e5ae93 h1:CFLw2b6vgOmpxsRWRiTd46tiR6YKg2crIuTu4cINYcY= -github.com/getlantern/go-tun2socks v1.16.12-0.20201218023150-b68f09e5ae93/go.mod h1:wgB2BFT8ZaPKyKOQ/5dljMG/YIow+AIXyq4KBwJ5sGQ= github.com/getlantern/golog v0.0.0-20190809085441-26e09e6dd330/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc= github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc= github.com/getlantern/golog v0.0.0-20200929154820-62107891371a/go.mod h1:ZyIjgH/1wTCl+B+7yH1DqrWp6MPJqESmwmEQ89ZfhvA= @@ -372,8 +374,6 @@ github.com/getlantern/rot13 v0.0.0-20220822172233-370767b2f782 h1:A1+qM0Dqm0no8A github.com/getlantern/rot13 v0.0.0-20220822172233-370767b2f782/go.mod h1:O0dNqH9hbXlOa9OpVdbACmTBfDPD+ENjbY0cPkzBd9g= github.com/getlantern/rotator v0.0.0-20160829164113-013d4f8e36a2 h1:smFR/kESUKlcdyatoOO3HngBzzrUU6S0LRw2vCBYQPg= github.com/getlantern/rotator v0.0.0-20160829164113-013d4f8e36a2/go.mod h1:Ap+QTDJeA24+0jjPHReq/LyP3ugEEDYvncluEgsm60A= -github.com/getlantern/safechannels v0.0.0-20201218194342-b4e5383e9627 h1:eYXtxjRyiP9f1rMvbnyT4DBWFEiIPY3zzMA5KqVshyc= -github.com/getlantern/safechannels v0.0.0-20201218194342-b4e5383e9627/go.mod h1:QJUudepmTj/KQXnReV6DZSJY/xO05fnfcZf3t8zkNLg= github.com/getlantern/shortcut v0.0.0-20211026183428-bf59a137fdec h1:8TfjIMydnhBs4edmXu2Nz1f2P0QOcXfol2rR1cxfrSs= github.com/getlantern/shortcut v0.0.0-20211026183428-bf59a137fdec/go.mod h1:3VQ6qvEBehqDBNbEKfNtSjK6MR5ydOdjMPgKjKay7vo= github.com/getlantern/telemetry v0.0.0-20230523155019-be7c1d8cd8cb h1:6XZ3Q4oD6A1Tjq6QLgzzQrdQ8FvulzW16HhNQOSECAM= @@ -778,7 +778,6 @@ github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1 github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -934,7 +933,6 @@ golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvx golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -960,7 +958,6 @@ golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1084,7 +1081,6 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200425043458-8463f397d07c/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= diff --git a/ios/client_test.go b/ios/client_test.go deleted file mode 100644 index ad61cea87..000000000 --- a/ios/client_test.go +++ /dev/null @@ -1,189 +0,0 @@ -package ios - -import ( - "fmt" - "io/ioutil" - "net" - "os" - "path/filepath" - "strconv" - "testing" - "time" - - "github.com/google/gopacket" - "github.com/google/gopacket/layers" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -const ( - mtu = 1500 - - userConfig = ` -deviceID: 12345678 -userID: 239316353 -token: WnVUQIhV9UJx0rVs-zTWs6sXkwEgce4Rp5kFiYos5ble_H2R60Qpdw -language: en-US -country: us -allowProbes: false` -) - -const ( - msg = "hello there" -) - -func TestClientUDP(t *testing.T) { - serverAddr, stopServer := startUDPEchoServer(t) - defer stopServer() - - serverHost, _serverPort, _ := net.SplitHostPort(serverAddr) - serverPort, _ := strconv.Atoi(_serverPort) - - tmpDir, err := ioutil.TempDir("", "client_test") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - err = ioutil.WriteFile(filepath.Join(tmpDir, "userconfig.yaml"), []byte(userConfig), 0644) - require.NoError(t, err) - - dialer := &realUDPDialer{receivedMessages: make(chan []byte, 100)} - w, err := Client(&noopResponseWriter{}, dialer, &noopMemChecker{}, tmpDir, mtu, "8.8.8.8", "8.8.4.4") - require.NoError(t, err) - - w.Write(udpPacket(t, serverHost, serverPort, msg)) - - select { - case echoed := <-dialer.receivedMessages: - assert.Equal(t, msg, string(echoed)) - case <-time.After(1 * time.Second): - t.Fatal("Timed out waiting for echo response") - } -} - -func startUDPEchoServer(t *testing.T) (string, func()) { - addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:0") - require.NoError(t, err) - conn, err := net.ListenUDP("udp", addr) - require.NoError(t, err) - go func() { - b := make([]byte, mtu) - for { - n, raddr, err := conn.ReadFromUDP(b) - if err != nil { - return - } - log.Debugf("Echoing: %v", string(b[:n])) - conn.WriteToUDP(b[:n], raddr) - } - }() - return conn.LocalAddr().String(), func() { - conn.Close() - } -} - -func udpPacket(t *testing.T, serverHost string, serverPort int, payload string) []byte { - ip := &layers.IPv4{ - Version: 4, - Protocol: layers.IPProtocolUDP, - SrcIP: net.ParseIP("127.0.0.1"), - DstIP: net.ParseIP(serverHost), - } - udp := &layers.UDP{ - SrcPort: 7890, - DstPort: layers.UDPPort(serverPort), - } - udp.SetNetworkLayerForChecksum(ip) - buf := gopacket.NewSerializeBuffer() - opts := gopacket.SerializeOptions{ - ComputeChecksums: true, - FixLengths: true, - } - err := gopacket.SerializeLayers(buf, opts, ip, udp, gopacket.Payload(payload)) - require.NoError(t, err) - return buf.Bytes() -} - -type noopResponseWriter struct { -} - -func (rw *noopResponseWriter) Write(b []byte) bool { - return true -} - -type realUDPDialer struct { - receivedMessages chan []byte -} - -func (d *realUDPDialer) Dial(host string, port int) UDPConn { - return &realUDPConn{receivedMessages: d.receivedMessages, host: host, port: port} -} - -type realUDPConn struct { - receivedMessages chan []byte - host string - port int - *net.UDPConn - cb *UDPCallbacks -} - -func (conn *realUDPConn) RegisterCallbacks(cb *UDPCallbacks) { - conn.cb = cb - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%v:%d", conn.host, conn.port)) - if err != nil { - conn.closeOnError(err) - return - } - - uconn, err := net.DialUDP("udp", nil, addr) - if err != nil { - conn.closeOnError(err) - return - } - - conn.UDPConn = uconn - conn.cb.OnDialSucceeded() -} - -func (conn *realUDPConn) WriteDatagram(b []byte) { - _, err := conn.UDPConn.Write(b) - if err != nil { - conn.cb.OnError(err) - } -} - -func (conn *realUDPConn) ReceiveDatagram() { - go func() { - b := make([]byte, mtu) - n, err := conn.UDPConn.Read(b) - if err != nil { - conn.cb.OnError(err) - return - } - conn.cb.OnReceive(b[:n]) - conn.receivedMessages <- b[:n] - }() -} - -func (conn *realUDPConn) Close() { - err := conn.UDPConn.Close() - if err != nil { - conn.cb.OnError(err) - } - conn.cb.OnClose() -} - -func (conn *realUDPConn) closeOnError(err error) { - conn.cb.OnError(err) - if conn.UDPConn != nil { - conn.UDPConn.Close() - } else { - conn.cb.OnClose() - } -} - -type noopMemChecker struct{} - -func (c *noopMemChecker) BytesRemain() int { - return 123456 -} diff --git a/ios/config.go b/ios/config.go deleted file mode 100644 index e6cd61e73..000000000 --- a/ios/config.go +++ /dev/null @@ -1,416 +0,0 @@ -package ios - -import ( - "bytes" - "compress/gzip" - "crypto/md5" - "encoding/hex" - "fmt" - "io" - "io/ioutil" - "net" - "net/http" - "net/http/httputil" - "os" - "path/filepath" - "time" - - "github.com/getlantern/errors" - "github.com/getlantern/fronted" - "github.com/getlantern/yaml" - - commonconfig "github.com/getlantern/common/config" - "github.com/getlantern/flashlight/v7/common" - "github.com/getlantern/flashlight/v7/config" - "github.com/getlantern/flashlight/v7/email" - "github.com/getlantern/flashlight/v7/embeddedconfig" - "github.com/getlantern/flashlight/v7/geolookup" -) - -const ( - userConfigYaml = "userconfig.yaml" - globalYaml = "global.yaml" - proxiesYaml = "proxies.yaml" -) - -// ConfigResult captures the result of calling Configure() -type ConfigResult struct { - // VPNNeedsReconfiguring indicates that something in the config changed and - // that the VPN needs to be reconfigured. - VPNNeedsReconfiguring bool - - // IPSToExcludeFromVPN lists all IPS that should be excluded from the VPNS's - // routes in a comma-delimited string - IPSToExcludeFromVPN string -} - -// Configure fetches updated configuration from the cloud and stores it in -// configFolderPath. There are 5 files that must be initialized in -// configFolderPath - global.yaml, global.yaml.etag, proxies.yaml, -// proxies.yaml.etag and masquerade_cache. deviceID should be a string that -// uniquely identifies the current device. hardcodedProxies allows manually specifying -// a proxies.yaml configuration that overrides whatever we fetch from the cloud. -func Configure(configFolderPath string, userID int, proToken, deviceID string, refreshProxies bool, hardcodedProxies string) (*ConfigResult, error) { - log.Debugf("Configuring client for device '%v' at config path '%v'", deviceID, configFolderPath) - defer log.Debug("Finished configuring client") - cf := &configurer{ - configFolderPath: configFolderPath, - hardcodedProxies: hardcodedProxies, - uc: userConfigFor(userID, proToken, deviceID), - } - return cf.configure(userID, proToken, refreshProxies) -} - -type UserConfig struct { - common.UserConfigData - Country string - AllowProbes bool -} - -type configurer struct { - configFolderPath string - hardcodedProxies string - uc *UserConfig - rt http.RoundTripper -} - -func (cf *configurer) configure(userID int, proToken string, refreshProxies bool) (*ConfigResult, error) { - result := &ConfigResult{} - - if err := cf.writeUserConfig(); err != nil { - return nil, err - } - - global, globalEtag, globalInitialized, err := cf.openGlobal() - if err != nil { - return nil, err - } - - proxies, proxiesEtag, proxiesInitialized, err := cf.openProxies() - if err != nil { - return nil, err - } - - result.VPNNeedsReconfiguring = globalInitialized || proxiesInitialized - - var globalUpdated, proxiesUpdated bool - - setupFronting := func() error { - log.Debug("Setting up fronting") - defer log.Debug("Set up fronting") - if frontingErr := cf.configureFronting(global, shortFrontedAvailableTimeout); frontingErr != nil { - log.Errorf("Unable to configure fronting on first try, update global config directly from GitHub and try again: %v", frontingErr) - global, globalUpdated = cf.updateGlobal(&http.Transport{}, global, globalEtag, "https://raw.githubusercontent.com/getlantern/lantern-binaries/main/cloud.yaml.gz") - return cf.configureFronting(global, longFrontedAvailableTimeout) - } - return nil - } - - if frontingErr := setupFronting(); frontingErr != nil { - log.Errorf("Unable to configure fronting, sticking with embedded configuration: %v", err) - } else { - log.Debug("Refreshing geolookup") - geolookup.Refresh() - - go func() { - cf.uc.Country = geolookup.GetCountry(1 * time.Minute) - log.Debugf("Successful geolookup: country %s", cf.uc.Country) - cf.uc.AllowProbes = global.FeatureEnabled( - config.FeatureProbeProxies, - common.Platform, - cf.uc.AppName, - "", - int64(cf.uc.UserID), - cf.uc.Token != "", - cf.uc.Country) - log.Debugf("Allow probes?: %v", cf.uc.AllowProbes) - if err := cf.writeUserConfig(); err != nil { - log.Errorf("Unable to save updated UserConfig with country and allow probes: %v", err) - } - }() - - log.Debug("Updating global config") - global, globalUpdated = cf.updateGlobal(cf.rt, global, globalEtag, "https://globalconfig.flashlightproxy.com/global.yaml.gz") - log.Debug("Updated global config") - if refreshProxies { - log.Debug("Refreshing proxies") - proxies, proxiesUpdated = cf.updateProxies(proxies, proxiesEtag) - log.Debug("Refreshed proxies") - } - - result.VPNNeedsReconfiguring = result.VPNNeedsReconfiguring || globalUpdated || proxiesUpdated - } - - for _, provider := range global.Client.Fronted.Providers { - for _, masquerade := range provider.Masquerades { - if len(result.IPSToExcludeFromVPN) == 0 { - result.IPSToExcludeFromVPN = masquerade.IpAddress - } else { - result.IPSToExcludeFromVPN = fmt.Sprintf("%v,%v", result.IPSToExcludeFromVPN, masquerade.IpAddress) - } - } - } - - for _, proxy := range proxies { - if proxy.Addr != "" { - host, _, _ := net.SplitHostPort(proxy.Addr) - result.IPSToExcludeFromVPN = fmt.Sprintf("%v,%v", host, result.IPSToExcludeFromVPN) - log.Debugf("Added %v", host) - } - if proxy.MultiplexedAddr != "" { - host, _, _ := net.SplitHostPort(proxy.MultiplexedAddr) - result.IPSToExcludeFromVPN = fmt.Sprintf("%v,%v", host, result.IPSToExcludeFromVPN) - log.Debugf("Added %v", host) - } - } - - email.SetDefaultRecipient(global.ReportIssueEmail) - - return result, nil -} - -func (cf *configurer) writeUserConfig() error { - bytes, err := yaml.Marshal(cf.uc) - if err != nil { - return errors.New("Unable to marshal user config: %v", err) - } - if writeErr := ioutil.WriteFile(cf.fullPathTo(userConfigYaml), bytes, 0644); writeErr != nil { - return errors.New("Unable to save userconfig.yaml: %v", err) - } - return nil -} - -func (cf *configurer) readUserConfig() (*UserConfig, error) { - bytes, err := ioutil.ReadFile(cf.fullPathTo(userConfigYaml)) - if err != nil { - return nil, errors.New("Unable to read userconfig.yaml: %v", err) - } - if len(bytes) == 0 { - return nil, errors.New("Empty userconfig.yaml") - } - uc := &UserConfig{} - if parseErr := yaml.Unmarshal(bytes, uc); parseErr != nil { - return nil, errors.New("Unable to parse userconfig.yaml: %v", err) - } - return uc, nil -} - -func (cf *configurer) openGlobal() (*config.Global, string, bool, error) { - cfg := &config.Global{} - etag, updated, err := cf.openConfig(globalYaml, cfg, embeddedconfig.Global) - return cfg, etag, updated, err -} - -func (cf *configurer) openProxies() (map[string]*commonconfig.ProxyConfig, string, bool, error) { - cfg := make(map[string]*commonconfig.ProxyConfig) - etag, updated, err := cf.openConfig(proxiesYaml, cfg, embeddedconfig.Proxies) - return cfg, etag, updated, err -} - -func (cf *configurer) openConfig(name string, cfg interface{}, embedded []byte) (string, bool, error) { - var initialized bool - bytes, err := ioutil.ReadFile(cf.fullPathTo(name)) - if err == nil && len(bytes) > 0 { - log.Debugf("Loaded %v from file", name) - } else { - log.Debugf("Initializing %v from embedded", name) - bytes = embedded - initialized = true - if writeErr := ioutil.WriteFile(cf.fullPathTo(name), bytes, 0644); writeErr != nil { - return "", false, errors.New("Unable to write embedded %v to disk: %v", name, writeErr) - } - } - if parseErr := yaml.Unmarshal(bytes, cfg); parseErr != nil { - return "", false, errors.New("Unable to parse %v: %v", name, parseErr) - } - etagBytes, err := ioutil.ReadFile(cf.fullPathTo(name + ".etag")) - if err != nil { - log.Debugf("No known etag for %v", name) - etagBytes = []byte{} - } - return string(etagBytes), initialized, nil -} - -func (cf *configurer) configureFronting(global *config.Global, timeout time.Duration) error { - log.Debug("Configuring fronting") - certs, err := global.TrustedCACerts() - if err != nil { - return errors.New("Unable to read trusted CAs from global config, can't configure domain fronting: %v", err) - } - - fronted.Configure(certs, global.Client.FrontedProviders(), "cloudfront", cf.fullPathTo("masquerade_cache")) - rt, ok := fronted.NewDirect(timeout) - if !ok { - return errors.New("Timed out waiting for fronting to finish configuring") - } - - cf.rt = rt - log.Debug("Configured fronting") - return nil -} - -func (cf *configurer) updateGlobal(rt http.RoundTripper, cfg *config.Global, etag string, url string) (*config.Global, bool) { - updated := &config.Global{} - didFetch, err := cf.updateFromWeb(rt, globalYaml, etag, updated, url) - if err != nil { - log.Error(err) - } - if didFetch { - cfg = updated - } - return cfg, didFetch -} - -func (cf *configurer) updateProxies(cfg map[string]*commonconfig.ProxyConfig, etag string) (map[string]*commonconfig.ProxyConfig, bool) { - updated := make(map[string]*commonconfig.ProxyConfig) - didFetch, err := cf.updateFromWeb(cf.rt, proxiesYaml, etag, updated, "http://config.getiantem.org/proxies.yaml.gz") - if err != nil { - log.Error(err) - } - if len(updated) == 0 { - log.Error("Proxies returned by config server was empty, ignoring") - didFetch = false - } - if didFetch { - cfg = updated - } - return cfg, didFetch -} - -// TODO: DRY violation with ../config/fetcher.go -func (cf *configurer) updateFromWeb(rt http.RoundTripper, name string, etag string, cfg interface{}, url string) (bool, error) { - var bytes []byte - var newETag string - var err error - - if name == proxiesYaml && cf.hardcodedProxies != "" { - bytes, newETag, err = cf.updateFromHardcodedProxies() - } else { - bytes, newETag, err = cf.doUpdateFromWeb(rt, name, etag, cfg, url) - } - if err != nil { - return false, err - } - - if bytes == nil { - // config unchanged - return false, nil - } - - cf.saveConfig(name, bytes) - cf.saveEtag(name, newETag) - - if name == proxiesYaml { - log.Debugf("Updated proxies.yaml from cloud:\n%v", string(bytes)) - } else { - log.Debugf("Updated %v from cloud", name) - } - - return newETag != etag, nil -} - -func (cf *configurer) doUpdateFromWeb(rt http.RoundTripper, name string, etag string, cfg interface{}, url string) ([]byte, string, error) { - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - return nil, "", errors.New("Unable to construct request to fetch %v from %v: %v", name, url, err) - } - - if etag != "" { - req.Header.Set(common.IfNoneMatchHeader, etag) - } - req.Header.Set("Accept", "application/x-gzip") - // Prevents intermediate nodes (domain-fronters) from caching the content - req.Header.Set("Cache-Control", "no-cache") - common.AddCommonHeaders(cf.uc, req) - - // make sure to close the connection after reading the Body - // this prevents the occasional EOFs errors we're seeing with - // successive requests - req.Close = true - - resp, err := rt.RoundTrip(req) - if err != nil { - return nil, "", errors.New("Unable to fetch cloud config at %s: %s", url, err) - } - dump, dumperr := httputil.DumpResponse(resp, false) - if dumperr != nil { - log.Errorf("Could not dump response: %v", dumperr) - } else { - log.Debugf("Response headers from %v:\n%v", url, string(dump)) - } - defer func() { - if closeerr := resp.Body.Close(); closeerr != nil { - log.Errorf("Error closing response body: %v", closeerr) - } - }() - - if resp.StatusCode == 304 { - log.Debugf("%v unchanged in cloud", name) - return nil, "", nil - } else if resp.StatusCode != 200 { - if dumperr != nil { - return nil, "", errors.New("Bad config response code for %v: %v", name, resp.StatusCode) - } - return nil, "", errors.New("Bad config resp for %v:\n%v", name, string(dump)) - } - - newEtag := resp.Header.Get(common.EtagHeader) - buf := &bytes.Buffer{} - body := io.TeeReader(resp.Body, buf) - gzReader, err := gzip.NewReader(body) - if err != nil { - return nil, "", errors.New("Unable to open gzip reader: %s", err) - } - - defer func() { - if err := gzReader.Close(); err != nil { - log.Errorf("Unable to close gzip reader: %v", err) - } - }() - - bytes, err := ioutil.ReadAll(gzReader) - if err != nil { - return nil, "", errors.New("Unable to read response for %v: %v", name, err) - } - - if parseErr := yaml.Unmarshal(bytes, cfg); parseErr != nil { - return nil, "", errors.New("Unable to parse update for %v: %v", name, parseErr) - } - - if newEtag == "" { - sum := md5.Sum(buf.Bytes()) - newEtag = hex.EncodeToString(sum[:]) - } - - return bytes, newEtag, nil -} - -func (cf *configurer) updateFromHardcodedProxies() ([]byte, string, error) { - return []byte(cf.hardcodedProxies), "hardcoded", nil -} - -func (cf *configurer) openFile(filename string) (*os.File, error) { - file, err := os.Open(cf.fullPathTo(filename)) - if err != nil { - err = errors.New("Unable to open %v: %v", filename, err) - } - return file, err -} - -func (cf *configurer) saveConfig(name string, bytes []byte) { - err := ioutil.WriteFile(cf.fullPathTo(name), bytes, 0644) - if err != nil { - log.Errorf("Unable to save config for %v: %v", name, err) - } -} - -func (cf *configurer) saveEtag(name string, etag string) { - err := ioutil.WriteFile(cf.fullPathTo(name+".etag"), []byte(etag), 0644) - if err != nil { - log.Errorf("Unable to save etag for %v: %v", name, err) - } -} - -func (cf *configurer) fullPathTo(filename string) string { - return filepath.Join(cf.configFolderPath, filename) -} diff --git a/ios/config_test.go b/ios/config_test.go deleted file mode 100644 index 742883f0e..000000000 --- a/ios/config_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package ios - -import ( - "os" - "path/filepath" - "sort" - "strings" - "testing" - "time" - - "github.com/fsnotify/fsnotify" - "github.com/getlantern/flashlight/v7/common" - "github.com/stretchr/testify/require" -) - -const ( - testDeviceID1 = "test1" - testDeviceID2 = "test2" -) - -func TestConfigure(t *testing.T) { - common.CompileTimeApplicationVersion = "8.0.0" - common.LibraryVersion = "8.0.0" - tmpDir, err := os.MkdirTemp("", "config_test") - require.NoError(t, err) - defer os.RemoveAll(tmpDir) - - os.WriteFile(filepath.Join(tmpDir, "global.yaml"), []byte{}, 0644) - os.WriteFile(filepath.Join(tmpDir, "global.yaml.etag"), []byte{}, 0644) - os.WriteFile(filepath.Join(tmpDir, "proxies.yaml"), []byte{}, 0644) - os.WriteFile(filepath.Join(tmpDir, "proxies.yaml.etag"), []byte{}, 0644) - os.WriteFile(filepath.Join(tmpDir, "masquerade_cache"), []byte{}, 0644) - os.WriteFile(filepath.Join(tmpDir, "userconfig.yaml"), []byte{}, 0644) - - const testUserId = 83901 - const testToken = "testToken" - result1, err := Configure(tmpDir, testUserId, testToken, testDeviceID1, true, "") - require.NoError(t, err) - - require.True(t, result1.VPNNeedsReconfiguring) - require.NotEmpty(t, result1.IPSToExcludeFromVPN) - - c := &configurer{configFolderPath: tmpDir} - uc, err := c.readUserConfig() - require.NoError(t, err) - require.Equal(t, testDeviceID1, uc.GetDeviceID()) - - time.Sleep(1 * time.Second) - watcher, err := fsnotify.NewWatcher() - require.NoError(t, err) - err = watcher.Add(c.fullPathTo(userConfigYaml)) - require.NoError(t, err) - result2, err := Configure(tmpDir, testUserId, testToken, testDeviceID2, true, "") - require.NoError(t, err) - ips1 := strings.Split(result1.IPSToExcludeFromVPN, ",") - ips2 := strings.Split(result2.IPSToExcludeFromVPN, ",") - sort.Strings(ips1) - sort.Strings(ips2) - if result2.VPNNeedsReconfiguring { - require.NotEqual(t, ips1, ips2) - } else { - require.Equal(t, ips1, ips2) - } - - // make sure the config file has been changed - <-watcher.Events - uc, err = c.readUserConfig() - require.NoError(t, err) - require.Equal(t, testDeviceID2, uc.GetDeviceID()) -} diff --git a/ios/demo/connmaker/connmaker.go b/ios/demo/connmaker/connmaker.go deleted file mode 100644 index 212a369fc..000000000 --- a/ios/demo/connmaker/connmaker.go +++ /dev/null @@ -1,12 +0,0 @@ -// connmaker is a utility for creating lots of TCP connections without closing them, to help stress the memory on the ios demo app -package main - -import ( - "net" -) - -func main() { - for i := 0; i < 1000; i++ { - net.Dial("tcp", "104.131.174.43:443") - } -} diff --git a/ios/demo/demo.go b/ios/demo/demo.go deleted file mode 100644 index d91cf3570..000000000 --- a/ios/demo/demo.go +++ /dev/null @@ -1,280 +0,0 @@ -// This demo program allows testing the iOS packet forwarding functionality -// on a desktop machine using a TUN device. -// -// Note - the demo currently doesn't support UDP. -// -// There are two ways to run the demo: -// -// To fetch configuration from the cloud, just like iOS does, run: -// -// ./demo -gw 192.168.1.1 -bypassthreads 100 -// -// Replace 192.168.1.1 with your default gateway (here and below as well). -// -// The -bypassthreads flag will enable automatic configuration of the routing -// table to bypass the demo TUN device for traffic to your proxy as well as -// domain fronting traffic. -// -// Alternately, to point at a specific proxies.yaml, run: -// -// ./demo -gw 192.168.1.1 -proxiesyaml ~/proxies.yaml -// -// To have the demo program handle all your internet traffic, run: -// -// sudo route delete default && sudo route add default 10.0.0.2 -// -// If using a proxies.yaml, you'll also need to manually set up a direct route -// for proxy traffic via the default gateway, like so: -// -// sudo route add 67.205.172.79 192.168.1.1 -// -// Now your network traffic will route through here to your proxy. -// -// When you're finished, you can fix your routing table with: -// -// sudo route delete default && sudo route add default 192.168.1.1 -// -// If you added a manual route for the proxy, you'll want to remove that too: -// -// sudo route delete 67.205.172.79 -// -// The utility scripts vpn.bash and direct.bash provide convenient ways to route traffic via the VPN -// or go back to routing traffic directly. You'll need to change the settings in routes.bash to match -// your system. -package main - -import ( - "encoding/base64" - "flag" - "fmt" - "io" - "io/ioutil" - "net/http" - _ "net/http/pprof" - "os" - "os/exec" - "os/signal" - "path/filepath" - "runtime" - "strings" - "sync" - "syscall" - - "github.com/eycorsican/go-tun2socks/tun" - - "github.com/getlantern/golog" - "github.com/getlantern/uuid" - - "github.com/getlantern/flashlight/v7/ios" -) - -var ( - log = golog.LoggerFor("ios-demo") -) - -var ( - tunDevice = flag.String("tun-device", "tun0", "tun device name") - tunAddr = flag.String("tun-address", "10.0.0.2", "tun device address") - tunMask = flag.String("tun-mask", "255.255.255.0", "tun device netmask") - tunGW = flag.String("tun-gw", "10.0.0.1", "tun device gateway") - pprofAddr = flag.String("pprofaddr", "", "pprof address to listen on, not activate pprof if empty") - internetGateway = flag.String("gw", "192.168.1.1", "gateway for getting to Internet") - userID = flag.Int("userid", 0, "user id to report to server") - proToken = flag.String("protoken", "", "pro token to report to server") - deviceID = flag.String("deviceid", base64.StdEncoding.EncodeToString(uuid.NodeID()), "deviceid to report to server") - bypassThreads = flag.Int("bypassthreads", 0, "number of threads to use for configuring bypass routes. If set to 0, we don't bypass.") - proxiesYaml = flag.String("proxiesyaml", "", "if specified, use the proxies.yaml at this location to configure client") - mtu = flag.Int("mtu", 1500, "mtu, defaults to 1500") -) - -type fivetuple struct { - proto string - srcIP, dstIP string - srcPort, dstPort int -} - -func (ft fivetuple) String() string { - return fmt.Sprintf("[%v] %v:%v -> %v:%v", ft.proto, ft.srcIP, ft.srcPort, ft.dstIP, ft.dstPort) -} - -func main() { - flag.Parse() - - if *pprofAddr != "" { - go func() { - log.Debugf("Starting pprof page at http://%s/debug/pprof", *pprofAddr) - srv := &http.Server{ - Addr: *pprofAddr, - } - if err := srv.ListenAndServe(); err != nil { - log.Error(err) - } - }() - } - - tmpDir, err := ioutil.TempDir("", "ios_demo") - if err != nil { - log.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - cfgResult, err := ios.Configure(tmpDir, *userID, *proToken, *deviceID, true, "") - if err != nil { - log.Fatal(err) - } - - if *proxiesYaml != "" { - log.Debugf("Using proxies.yaml at %v", *proxiesYaml) - in, readErr := ioutil.ReadFile(*proxiesYaml) - if readErr != nil { - log.Fatal(readErr) - } - writeErr := ioutil.WriteFile(filepath.Join(tmpDir, "proxies.yaml"), in, 0644) - if writeErr != nil { - log.Fatal(writeErr) - } - } - - dev, err := tun.OpenTunDevice(*tunDevice, *tunAddr, *tunGW, *tunMask, []string{"8.8.8.8"}, false) - if err != nil { - log.Fatalf("Error opening TUN device: %v", err) - } - defer dev.Close() - - ch := make(chan os.Signal, 1) - signal.Notify(ch, - syscall.SIGHUP, - syscall.SIGINT, - syscall.SIGTERM, - syscall.SIGQUIT, - syscall.SIGPIPE) - go func() { - <-ch - log.Debug("Stopping TUN device") - dev.Close() - log.Debug("Stopped TUN device") - }() - - doneAddingBypassRoutes := make(chan interface{}) - - ipsToExclude := strings.Split(cfgResult.IPSToExcludeFromVPN, ",") - if *bypassThreads > 0 { - defer func() { - <-doneAddingBypassRoutes - log.Debugf("Deleting bypass routes for %d ips", len(ipsToExclude)) - - ipsCh := make(chan string) - var wg sync.WaitGroup - wg.Add(*bypassThreads) - for i := 0; i < *bypassThreads; i++ { - go func() { - for ip := range ipsCh { - if deleteErr := exec.Command("sudo", "route", "delete", ip).Run(); deleteErr != nil { - log.Errorf("Error deleting route fpr %v: %v", ip, deleteErr) - } - } - wg.Done() - }() - } - for i, ip := range ipsToExclude { - ipsCh <- ip - if i > 0 && i%50 == 0 { - log.Debugf("Deleting bypass routes ... %d", i) - } - } - close(ipsCh) - wg.Wait() - - log.Debugf("Deleted bypass routes for %d ips", len(ipsToExclude)) - }() - - go func() { - log.Debugf("Adding bypass routes for %d ips", len(ipsToExclude)) - - ipsCh := make(chan string) - var wg sync.WaitGroup - wg.Add(*bypassThreads) - for i := 0; i < *bypassThreads; i++ { - go func() { - for ip := range ipsCh { - if addErr := exec.Command("sudo", "route", "add", ip, *internetGateway).Run(); addErr != nil { - log.Error(addErr) - } - } - wg.Done() - }() - } - for i, ip := range ipsToExclude { - ipsCh <- ip - if i > 0 && i%50 == 0 { - log.Debugf("Adding bypass routes ... %d", i) - } - } - close(ipsCh) - wg.Wait() - - log.Debugf("Added bypass routes for %d ips", len(ipsToExclude)) - close(doneAddingBypassRoutes) - }() - } - - ios.SetProfilePath("/tmp/") - writer, err := ios.Client(&writerAdapter{dev}, &noopUDPDialer{}, &noopMemChecker{}, tmpDir, *mtu, "8.8.8.8", "8.8.4.4") - if err != nil { - log.Fatal(err) - } - - log.Debug("Reading from TUN device") - b := make([]byte, *mtu) - for { - n, err := dev.Read(b) - if n > 0 { - dataCap, _ := writer.Write(b[:n]) - if dataCap > 0 { - log.Debugf("Data capped at %dMiB", dataCap) - } - } - if err != nil { - if err != io.EOF { - log.Errorf("Unexpected error reading from TUN device: %v", err) - } - return - } - } -} - -type noopMemChecker struct{} - -func (c *noopMemChecker) BytesRemain() int { - memstats := &runtime.MemStats{} - runtime.ReadMemStats(memstats) - // This gives us a positive but not immensely large bytes remaining - return 8000000 - int(memstats.HeapInuse) -} - -type writerAdapter struct { - Writer io.Writer -} - -func (wa *writerAdapter) Write(b []byte) bool { - _, err := wa.Writer.Write(b) - return err == nil -} - -type noopUDPDialer struct{} - -func (d *noopUDPDialer) Dial(host string, port int) ios.UDPConn { - return &noopUDPConn{} -} - -type noopUDPConn struct{} - -// RegisterCallbacks registers lifecycle callbacks for the connection. Clients of the UDPConn -// must call this before trying to use WriteDatagram and ReceiveDatagram. -func (conn *noopUDPConn) RegisterCallbacks(cb *ios.UDPCallbacks) {} - -func (conn *noopUDPConn) WriteDatagram([]byte) {} - -func (conn *noopUDPConn) ReceiveDatagram() {} - -func (conn *noopUDPConn) Close() {} diff --git a/ios/demo/direct.bash b/ios/demo/direct.bash deleted file mode 100755 index 94f2821b8..000000000 --- a/ios/demo/direct.bash +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -source ./routes.bash - -sudo route delete default -sudo route delete $PROXY -sudo route delete $REAL_DNS -sudo route add default $GATEWAY \ No newline at end of file diff --git a/ios/demo/routes.bash b/ios/demo/routes.bash deleted file mode 100755 index 9f9242cfb..000000000 --- a/ios/demo/routes.bash +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -export PROXY=167.172.232.184 -export REAL_DNS=8.8.4.4 -export GATEWAY=192.168.1.1 -export TUN_GATEWAY=10.0.0.2 \ No newline at end of file diff --git a/ios/demo/vpn.bash b/ios/demo/vpn.bash deleted file mode 100755 index 4121566d5..000000000 --- a/ios/demo/vpn.bash +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -source ./routes.bash - -sudo route delete default -sudo route add $PROXY $GATEWAY -sudo route add $REAL_DNS $GATEWAY -sudo route add default $TUN_GATEWAY \ No newline at end of file diff --git a/ios/ios.go b/ios/ios.go deleted file mode 100644 index b409ce55b..000000000 --- a/ios/ios.go +++ /dev/null @@ -1,308 +0,0 @@ -package ios - -import ( - "fmt" - "io" - "os" - "path/filepath" - "sync" - "time" - - tun2socks "github.com/eycorsican/go-tun2socks/core" - - "github.com/getlantern/common/config" - "github.com/getlantern/dnsgrab" - "github.com/getlantern/dnsgrab/persistentcache" - "github.com/getlantern/errors" - - "github.com/getlantern/flashlight/v7/bandit" - "github.com/getlantern/flashlight/v7/bandwidth" - "github.com/getlantern/flashlight/v7/buffers" - "github.com/getlantern/flashlight/v7/chained" - "github.com/getlantern/flashlight/v7/common" -) - -const ( - maxDNSGrabAge = 1 * time.Hour // this doesn't need to be long because our fake DNS records have a TTL of only 1 second. We use a smaller value than on Android to be conservative with memory usage. - - quotaSaveInterval = 1 * time.Minute - shortFrontedAvailableTimeout = 30 * time.Second - longFrontedAvailableTimeout = 5 * time.Minute - - logMemoryInterval = 5 * time.Second - forceGCInterval = 250 * time.Millisecond - - dialTimeout = 30 * time.Second - shortIdleTimeout = 5 * time.Second - closeTimeout = 1 * time.Second - - maxConcurrentDials = 2 - ipWriteBufferDepth = 100 - downstreamWriteBufferDepth = 100 -) - -type Writer interface { - Write([]byte) bool -} - -type writeRequest struct { - b []byte - ok chan bool -} - -type writerAdapter struct { - writer Writer - requests chan *writeRequest - closeOnce sync.Once -} - -func newWriterAdapter(writer Writer) io.WriteCloser { - wa := &writerAdapter{ - writer: writer, - requests: make(chan *writeRequest, ipWriteBufferDepth), - } - - // MEMORY_OPTIMIZATION - handle all writing of output packets on a single goroutine to avoid creating more native threads - go wa.handleWrites() - return wa -} - -func (wa *writerAdapter) Write(b []byte) (int, error) { - req := &writeRequest{ - b: b, - ok: make(chan bool), - } - wa.requests <- req - ok := <-req.ok - if !ok { - return 0, errors.New("error writing") - } - return len(b), nil -} - -func (wa *writerAdapter) handleWrites() { - for req := range wa.requests { - req.ok <- wa.writer.Write(req.b) - } -} - -func (wa *writerAdapter) Close() error { - wa.closeOnce.Do(func() { - close(wa.requests) - }) - return nil -} - -type ClientWriter interface { - // Write writes the given bytes. As a side effect of writing, we periodically - // record updated bandwidth quota information in the configured quota.txt file. - // If user has exceeded bandwidth allowance, returns a positive integer - // representing the bandwidth allowance. - Write([]byte) (int, error) - - // Reconfigure forces the ClientWriter to update its configuration - Reconfigure() - - Close() error -} - -type cw struct { - ipStack io.WriteCloser - client *client - dialer *bandit.BanditDialer - quotaTextPath string - lastSavedQuota time.Time -} - -func (c *cw) Write(b []byte) (int, error) { - _, err := c.ipStack.Write(b) - - result := 0 - if time.Since(c.lastSavedQuota) > quotaSaveInterval { - c.lastSavedQuota = time.Now() - - quota, tracked := bandwidth.GetQuota() - // Only save if quota has actually been tracked already - if tracked { - if quota != nil && quota.MiBUsed > quota.MiBAllowed { - result = int(quota.MiBAllowed) - } - - go func() { - if quota == nil { - log.Debug("Clearing bandwidth quota file") - writeErr := os.WriteFile(c.quotaTextPath, []byte{}, 0644) - if writeErr != nil { - log.Errorf("Unable to clear quota file: %v", writeErr) - } - } else { - log.Debugf("Saving bandwidth quota file with %d/%d", quota.MiBUsed, quota.MiBAllowed) - writeErr := os.WriteFile(c.quotaTextPath, []byte(fmt.Sprintf("%d/%d", quota.MiBUsed, quota.MiBAllowed)), 0644) - if writeErr != nil { - log.Errorf("Unable to write quota file: %v", writeErr) - } - } - }() - } - } - - return result, err -} - -func (c *cw) Reconfigure() { - dialers, err := c.client.loadDialers() - if err != nil { - // this causes the NetworkExtension process to die. Since the VPN is configured as "on-demand", - // the OS will automatically restart the service, at which point we'll read the new config anyway. - panic(log.Errorf("Unable to load dialers on reconfigure: %v", err)) - } - - c.dialer, err = bandit.New(dialers) - if err != nil { - log.Errorf("Unable to create dialer on reconfigure: %v", err) - } -} - -func (c *cw) Close() error { - c.dialer.Close() - c.client.packetsOut.Close() - return nil -} - -type client struct { - packetsOut io.WriteCloser - udpDialer UDPDialer - memChecker MemChecker - configDir string - mtu int - capturedDNSHost string - realDNSHost string - uc *UserConfig - tcpHandler *proxiedTCPHandler - udpHandler *directUDPHandler - //ipStack tun2socks.LWIPStack - clientWriter *cw - memoryAvailable int64 - started time.Time -} - -func Client(packetsOut Writer, udpDialer UDPDialer, memChecker MemChecker, configDir string, mtu int, capturedDNSHost, realDNSHost string) (ClientWriter, error) { - if mtu <= 0 { - log.Debug("Defaulting MTU to 1500") - mtu = 1500 - } - - c := &client{ - packetsOut: newWriterAdapter(packetsOut), - udpDialer: udpDialer, - memChecker: memChecker, - configDir: configDir, - mtu: mtu, - capturedDNSHost: capturedDNSHost, - realDNSHost: realDNSHost, - started: time.Now(), - } - - c.optimizeMemoryUsage() - go c.gcPeriodically() - go c.logMemory() - - return c.start() -} - -func (c *client) start() (ClientWriter, error) { - if err := c.loadUserConfig(); err != nil { - return nil, log.Errorf("error loading user config: %v", err) - } - - log.Debugf("Running client for device '%v' at config path '%v'", c.uc.GetDeviceID(), c.configDir) - log.Debugf("Max buffer bytes: %d", buffers.MaxBufferBytes()) - - dialers, err := c.loadDialers() - if err != nil { - return nil, err - } - dialer, err := bandit.New(dialers) - if err != nil { - return nil, err - } - - // We use a persistent cache for dnsgrab because some clients seem to hang on to our fake IP addresses for a while, even though we set a TTL of 1 second. - // That can be a problem when the network extension is automatically restarted. Caching the dns cache on disk allows us to successfully reverse look up - // those IP addresses even after a restart. - cacheFile := filepath.Join(c.configDir, "dnsgrab.cache") - cache, err := persistentcache.New(cacheFile, maxDNSGrabAge) - if err != nil { - return nil, errors.New("Unable to initialize dnsgrab cache at %v: %v", cacheFile, err) - } - grabber, err := dnsgrab.ListenWithCache( - "127.0.0.1:0", - func() string { return c.realDNSHost }, - cache, - ) - if err != nil { - return nil, errors.New("Unable to start dnsgrab: %v", err) - } - - c.tcpHandler = newProxiedTCPHandler(c, dialer, grabber) - c.udpHandler = newDirectUDPHandler(c, c.udpDialer, grabber, c.capturedDNSHost) - - ipStack := tun2socks.NewLWIPStack() - tun2socks.RegisterOutputFn(c.packetsOut.Write) - tun2socks.RegisterTCPConnHandler(c.tcpHandler) - tun2socks.RegisterUDPConnHandler(c.udpHandler) - - freeMemory() - - c.clientWriter = &cw{ - ipStack: ipStack, - client: c, - dialer: dialer, - quotaTextPath: filepath.Join(c.configDir, "quota.txt"), - } - - return c.clientWriter, nil -} - -func (c *client) loadUserConfig() error { - cf := &configurer{configFolderPath: c.configDir} - uc, err := cf.readUserConfig() - if err != nil { - return err - } - c.uc = uc - return nil -} - -func (c *client) loadDialers() ([]bandit.Dialer, error) { - cf := &configurer{configFolderPath: c.configDir} - chained.PersistSessionStates(c.configDir) - - proxies := make(map[string]*config.ProxyConfig) - _, _, err := cf.openConfig(proxiesYaml, proxies, []byte{}) - if err != nil { - return nil, err - } - - dialers := chained.CreateDialers(c.configDir, proxies, c.uc) - chained.TrackStatsFor(dialers, c.configDir) - return dialers, nil -} - -func partialUserConfigFor(deviceID string) *UserConfig { - return userConfigFor(0, "", deviceID) -} - -func userConfigFor(userID int, proToken, deviceID string) *UserConfig { - // TODO: plug in implementation of fetching timezone for iOS to work around https://github.com/golang/go/issues/20455 - return &UserConfig{ - UserConfigData: *common.NewUserConfigData( - "Lantern", - deviceID, - int64(userID), - proToken, - nil, // Headers currently unused - "", // Language currently unused - ), - } -} diff --git a/ios/log.go b/ios/log.go deleted file mode 100644 index cae8b633a..000000000 --- a/ios/log.go +++ /dev/null @@ -1,29 +0,0 @@ -package ios - -import ( - "github.com/getlantern/flashlight/v7/ios/logger" - "github.com/getlantern/golog" -) - -var ( - log = golog.LoggerFor("ios") - statsLog = golog.LoggerFor("ios.stats") - swiftLog = golog.LoggerFor("ios.swift") -) - -// ConfigureFileLogging configures logging to log to files at the given fullLogFilePath -// and capture heap and goroutine profiles at the given profile path. -func ConfigureFileLogging(fullLogFilePath string, profilePath string) error { - SetProfilePath(profilePath) - return logger.ConfigureFileLogging(fullLogFilePath) -} - -// LogDebug logs the given msg to the swift logger at debug level -func LogDebug(msg string) { - swiftLog.Debug(msg) -} - -// LogError logs the given msg to the swift logger at error level -func LogError(msg string) { - swiftLog.Error(msg) -} diff --git a/ios/logger/logger_darwin.go b/ios/logger/logger_darwin.go deleted file mode 100644 index 2437b0608..000000000 --- a/ios/logger/logger_darwin.go +++ /dev/null @@ -1,86 +0,0 @@ -package logger - -// #include -// -// void log_debug(const char *msg) -// { -// os_log_debug(OS_LOG_DEFAULT, "%{public}s", msg); -// } -// -// void log_error(const char *msg) -// { -// os_log_error(OS_LOG_DEFAULT, "%{public}s", msg); -// } -import "C" - -import ( - "io" - "os" - "path/filepath" - "strings" - - "github.com/getlantern/flashlight/v7/logging" - "github.com/getlantern/golog" -) - -func init() { - golog.SetOutputs(defaultLoggers()) -} - -// ConfigureFileLogging configures file logging to use the lantern.log file at -// the given path. It is required that the file, as well as files lantern.log.1 -// through lantern.log.5 already exist so that they are writeable from the Go -// side. -func ConfigureFileLogging(fullLogFilePath string) error { - logFileDirectory, filename := filepath.Split(fullLogFilePath) - appName := strings.Split(filename, ".")[0] - werr, wout := defaultLoggers() - return logging.EnableFileLoggingWith(werr, wout, appName, logFileDirectory, 10, 10) -} - -func defaultLoggers() (io.WriteCloser, io.WriteCloser) { - return &dualWriter{os.Stderr, loggerWith(func(msg string) { - C.log_error(C.CString(msg)) - })}, - &dualWriter{os.Stdout, loggerWith(func(msg string) { - C.log_debug(C.CString(msg)) - })} -} - -type dualWriter struct { - w1 io.Writer - w2 io.WriteCloser -} - -func (dw *dualWriter) Write(b []byte) (int, error) { - n, err := dw.w1.Write(b) - n2, err2 := dw.w2.Write(b) - if n2 < n { - n = n2 - } - if err == nil { - err = err2 - } - return n, err -} - -func (dw *dualWriter) Close() error { - return dw.w2.Close() -} - -func loggerWith(fn func(string)) io.WriteCloser { - return &loggerFn{fn} -} - -type loggerFn struct { - log func(string) -} - -func (lf *loggerFn) Write(msg []byte) (int, error) { - lf.log(string(msg)) - return len(msg), nil -} - -func (lf *loggerFn) Close() error { - return nil -} diff --git a/ios/logger/logger_other.go b/ios/logger/logger_other.go deleted file mode 100644 index 647d4e8f6..000000000 --- a/ios/logger/logger_other.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build !windows && !darwin -// +build !windows,!darwin - -package logger - -import ( - "github.com/getlantern/errors" -) - -func ConfigureFileLogging(fullLogFilePath string) error { - return errors.New("ConfigureFileLogging is only supported on darwin") -} diff --git a/ios/memory.go b/ios/memory.go deleted file mode 100644 index 45d8368e8..000000000 --- a/ios/memory.go +++ /dev/null @@ -1,186 +0,0 @@ -package ios - -import ( - "context" - "net" - "os" - "path/filepath" - "runtime" - "runtime/debug" - "runtime/pprof" - "sync" - "sync/atomic" - "time" - - "github.com/dustin/go-humanize" - - "github.com/getlantern/flashlight/v7/chained" - "github.com/getlantern/netx" -) - -// Memory management on iOS is critical because we're running in a network extension that's limited to 15 MB of memory. We handle this using several techniques. -// -// All places in the code that do odd stuff in order to help with memory optimization are marked with MEMORY_OPTIMIZATION -// -// 1. Limit the number of goroutines that write to/from lwip in order to limit the number of OS threads (each OS thread has 0.5MB of stack allocated to it, which gets big pretty quickly) -// 2. Limit concurrency for dialing upstream in order to limit the memory involved with public key cryptography -// 3. Use a fork of go-tun2socks tuned for low memory usage (see https://lwip.fandom.com/wiki/Tuning_TCP and https://lwip.fandom.com/wiki/Lwipopts.h) -// 4. Set an aggressive GCPercent -// 5. Use Go 1.14 instead of 1.15 (seems to have lower memory usage for some reason) -// 6. Use short idle timeouts to reduce the number of simultaneously open connections -// 7. Use small send and receive buffers for upstream TCP connections, adapting to the available amount of system memory - -var ( - profilePath string - profilePathMx sync.RWMutex -) - -func SetProfilePath(path string) { - profilePathMx.Lock() - defer profilePathMx.Unlock() - profilePath = path -} - -func getProfilePath() string { - profilePathMx.Lock() - defer profilePathMx.Unlock() - return profilePath -} - -// MemChecker checks the system's memory level -type MemChecker interface { - // BytesRemain returns the number of bytes of memory left before we hit the system limit - BytesRemain() int -} - -func (c *client) optimizeMemoryUsage() { - // MEMORY_OPTIMIZATION - limit the number of CPUs used to reduce the number of OS threads (and associated stack) to keep memory usage down - runtime.GOMAXPROCS(1) - - // MEMORY_OPTIMIZATION - set very aggressive IdleTimeout to help deal with memory constraints on iOS - chained.IdleTimeout = shortIdleTimeout - - // MEMORY_OPTIMIZATION - set an aggressive target for triggering GC after new allocations reach 20% of heap - debug.SetGCPercent(20) - - var dialer net.Dialer - netx.OverrideDial(func(ctx context.Context, network, addr string) (net.Conn, error) { - conn, err := dialer.DialContext(ctx, network, addr) - if err == nil { - tcpConn, ok := conn.(*net.TCPConn) - if ok { - // MEMORY_OPTIMIZATION - set small send and receive buffers for cases where we have lots of connections and a flaky network - // This can reduce throughput, especially on networks with high packet loss. - bytesRemain := int(atomic.LoadInt64(&c.memoryAvailable)) - bufferSize := bytesRemain / 25 // this factor gives us a buffer size of about 80KB when remaining memory is about 2MB. - if bufferSize < 4096 { - // never go smaller than 4096 - bufferSize = 4096 - } - tcpConn.SetWriteBuffer(bufferSize) - tcpConn.SetReadBuffer(bufferSize) - } - } - return conn, err - }) -} - -func (c *client) logMemory() { - for { - c.doLogMemory() - time.Sleep(logMemoryInterval) - } -} - -func (c *client) gcPeriodically() { - ticker := time.NewTicker(forceGCInterval) - for range ticker.C { - // this select ensures that if ticker fired while we were checking memory (i.e. it took longer than forceGCInterval), we wait until the ticket fires again to check memory - select { - case <-ticker.C: - continue - default: - freeMemory() - atomic.StoreInt64(&c.memoryAvailable, int64(c.memChecker.BytesRemain())) - } - } -} - -func (c *client) doLogMemory() { - bytesRemain := atomic.LoadInt64(&c.memoryAvailable) - if bytesRemain < 0 { - bytesRemain = 0 - } - - memstats := &runtime.MemStats{} - runtime.ReadMemStats(memstats) - - numOSThreads, _ := runtime.ThreadCreateProfile(nil) - statsLog.Debugf("Memory System Bytes Remain: %v Num OS Threads: %d Go InUse: %v", - humanize.Bytes(uint64(bytesRemain)), - numOSThreads, - humanize.Bytes(memstats.HeapInuse)) - - stats := debug.GCStats{ - PauseQuantiles: make([]time.Duration, 10), - } - debug.ReadGCStats(&stats) - - elapsed := time.Now().Sub(c.started) - statsLog.Debugf("Memory GC num: %v total pauses: %v (%.2f%%) pause percentiles: %v", stats.NumGC, stats.PauseTotal, float64(stats.PauseTotal)*100/float64(elapsed), stats.PauseQuantiles) -} - -func freeMemory() { - debug.FreeOSMemory() // this calls garbage collection before freeing memory to the OS -} - -func captureProfiles() { - log.Debug("Capturing profiles") - - // always free memory before capturing profiles because we need at least one GC before capturing heap data to get appropriate stats - freeMemory() - - path := getProfilePath() - if path == "" { - log.Error("No profile path set, can't capture profiles") - return - } - - heap, err := os.OpenFile(filepath.Join(path, "heap.profile.tmp"), os.O_TRUNC|os.O_CREATE|os.O_RDWR|os.O_SYNC, 0644) - if err != nil { - log.Errorf("Unable to open heap profile file %v for writing: %v", path, err) - return - } - defer heap.Close() - - goroutine, err := os.OpenFile(filepath.Join(path, "goroutine_profile.txt.tmp"), os.O_TRUNC|os.O_CREATE|os.O_RDWR|os.O_SYNC, 0644) - if err != nil { - log.Errorf("Unable to open heap profile file %v for writing: %v", path, err) - return - } - defer goroutine.Close() - - err = pprof.WriteHeapProfile(heap) - if err != nil { - log.Errorf("Unable to capture heap profile: %v", err) - } else { - err = os.Rename(filepath.Join(path, "heap.profile.tmp"), filepath.Join(path, "heap.profile")) - if err != nil { - log.Errorf("Unable to rename heap profile: %v", err) - } else { - log.Debugf("Captured heap profile") - } - } - - err = pprof.Lookup("goroutine").WriteTo(goroutine, 1) - if err != nil { - log.Errorf("Unable to capture goroutine profile: %v", err) - } else { - err = os.Rename(filepath.Join(path, "goroutine_profile.txt.tmp"), filepath.Join(path, "goroutine_profile.txt")) - if err != nil { - log.Errorf("Unable to rename goroutine profile: %v", err) - } else { - log.Debugf("Captured goroutine profile") - } - } -} diff --git a/ios/pro.go b/ios/pro.go deleted file mode 100644 index 64c268551..000000000 --- a/ios/pro.go +++ /dev/null @@ -1,219 +0,0 @@ -package ios - -import ( - "net/http" - "strings" - "sync" - "time" - - "github.com/getlantern/fronted" - - "github.com/getlantern/flashlight/v7/common" - proclient "github.com/getlantern/flashlight/v7/pro/client" -) - -// ProCredentials are credentials that authenticate a pro account -type ProCredentials struct { - UserID int - ProToken string -} - -// IsActiveProDevice checks whether the given device is an active pro device -func IsActiveProDevice(userID int, proToken, deviceID string) (bool, error) { - pc, err := getProClient() - if err != nil { - return false, err - } - - resp, err := pc.UserData(userConfigFor(userID, proToken, deviceID)) - if err != nil { - if strings.Contains(err.Error(), "Not authorized") { - // This means that the user_id and pro_token are no good, which means this can't be an active pro device - return false, nil - } - return false, log.Errorf("unable to fetch user data: %v", err) - } - - user := resp.User - if user.UserStatus != "active" { - log.Debug("pro account not active") - return false, nil - } - - for _, device := range user.Devices { - if device.Id == deviceID { - return true, nil - } - } - - log.Debug("device is not linked to pro account") - return false, nil -} - -// RecoverProAccount attempts to recover an existing Pro account linked to this email address and device ID -func RecoverProAccount(deviceID, emailAddress string) (*ProCredentials, error) { - pc, err := getProClient() - if err != nil { - return nil, err - } - - resp, err := pc.RecoverProAccount(partialUserConfigFor(deviceID), emailAddress) - if err != nil { - return nil, log.Errorf("unable to recover pro account: %v", err) - } - - return &ProCredentials{UserID: resp.UserID, ProToken: resp.ProToken}, nil -} - -// RequestRecoveryEmail requests an account recovery email for linking to an existing pro account -func RequestRecoveryEmail(deviceID, deviceName, emailAddress string) error { - pc, err := getProClient() - if err != nil { - return err - } - - err = pc.RequestRecoveryEmail(partialUserConfigFor(deviceID), deviceName, emailAddress) - if err != nil { - return log.Errorf("unable to request recovery email: %v", err) - } - - return nil -} - -// ValidateRecoveryCode validates the given recovery code and finishes linking the device, returning the user_id and pro_token for the account. -func ValidateRecoveryCode(deviceID, code string) (*ProCredentials, error) { - pc, err := getProClient() - if err != nil { - return nil, err - } - - resp, err := pc.ValidateRecoveryCode(partialUserConfigFor(deviceID), code) - if err != nil { - return nil, log.Errorf("unable to validate recovery code: %v", err) - } - - return &ProCredentials{UserID: resp.UserID, ProToken: resp.ProToken}, nil -} - -// RequestDeviceLinkingCode requests a new device linking code to allow linking the current device to a pro account via an existing pro device. -func RequestDeviceLinkingCode(deviceID, deviceName string) (string, error) { - pc, err := getProClient() - if err != nil { - return "", err - } - - resp, err := pc.RequestDeviceLinkingCode(partialUserConfigFor(deviceID), deviceName) - if err != nil { - return "", log.Errorf("unable to request link code: %v", err) - } - - return resp.Code, nil -} - -// RedeemResellerCode redeems the given reseller code, returning the payment_status and the plan -func RedeemResellerCode(userID int, proToken, deviceID, emailAddress, resellerCode, deviceName, currency string) error { - pc, err := getProClient() - if err != nil { - return err - } - - resp, err := pc.RedeemResellerCode( - userConfigFor( - userID, - proToken, - deviceID), - emailAddress, resellerCode, deviceName, currency) - if err != nil { - return log.Errorf("unable to redeem reseller code: %v", err) - } - log.Debugf("Redeemed reseller code, response: %+v", resp) - return nil -} - -// UserCreate creates a new user account on the pro server -func UserCreate(deviceID string) (*ProCredentials, error) { - pc, err := getProClient() - if err != nil { - return nil, err - } - - resp, err := pc.UserCreate(partialUserConfigFor(deviceID)) - if err != nil { - return nil, log.Errorf("unable to create new user: %v", err) - } - log.Debugf("created new user: %+v", resp) - return &ProCredentials{UserID: int(resp.ID), ProToken: resp.Token}, nil -} - -// Canceler providers a mechanism for canceling long running operations -type Canceler struct { - c chan interface{} - cancelOnce sync.Once -} - -// Cancel cancels an operation -func (c *Canceler) Cancel() { - c.cancelOnce.Do(func() { - close(c.c) - }) -} - -// NewCanceler creates a Canceller -func NewCanceler() *Canceler { - return &Canceler{c: make(chan interface{})} -} - -// ValidateDeviceLinkingCode validates a device linking code to allow linking the current device to a pro account via an existing pro device. -// It will keep trying until it succeeds or the supplied Canceler is canceled. In the case of cancel, it will return nil credentials and a nil -// error. -func ValidateDeviceLinkingCode(c *Canceler, deviceID, deviceName, code string) (*ProCredentials, error) { - pc, err := getProClient() - if err != nil { - return nil, err - } - - overallTimeout := time.After(5 * time.Minute) - retryDelay := 5 * time.Second - maxRetryDelay := 30 * time.Second - for { - resp, err := pc.ValidateDeviceLinkingCode(partialUserConfigFor(deviceID), deviceName, code) - if err == nil { - return &ProCredentials{UserID: resp.UserID, ProToken: resp.ProToken}, nil - } - - err = log.Errorf("unable to validate recovery code: %v", err) - select { - case <-time.After(retryDelay): - retryDelay *= 2 - if retryDelay > maxRetryDelay { - retryDelay = maxRetryDelay - } - log.Debugf("trying to validate recovery code again") - continue - - case <-c.c: - log.Debug("validating recovery code canceled") - return nil, nil - - case <-overallTimeout: - return nil, log.Error("validating recovery code timed out") - } - } - -} - -func getProClient() (*proclient.Client, error) { - rt, ok := fronted.NewDirect(longFrontedAvailableTimeout) - if !ok { - return nil, log.Errorf("timed out waiting for fronting to finish configuring") - } - - pc := proclient.NewClient(&http.Client{ - Transport: rt, - Timeout: 30 * time.Second, - }, func(req *http.Request, uc common.UserConfig) { - common.AddCommonHeaders(uc, req) - }) - - return pc, nil -} diff --git a/ios/tcp.go b/ios/tcp.go deleted file mode 100644 index 57e6497d5..000000000 --- a/ios/tcp.go +++ /dev/null @@ -1,214 +0,0 @@ -package ios - -import ( - "context" - "fmt" - "io" - "net" - "runtime" - "sync" - "sync/atomic" - "time" - - "github.com/getlantern/dnsgrab" - "github.com/getlantern/flashlight/v7/bandit" - "github.com/getlantern/idletiming" - "github.com/getlantern/netx" -) - -type dialRequest struct { - ctx context.Context - addr string - upstream chan net.Conn - err chan error -} - -// proxiedTCPHandler implements TCPConnHandler from go-tun2socks by routing TCP connections -// via our proxies. -type proxiedTCPHandler struct { - dialOut func(ctx context.Context, network, addr string) (net.Conn, error) - client *client - grabber dnsgrab.Server - mtu int - dialRequests chan *dialRequest - downstreamWriteWorker *worker - upstreams map[io.Closer]io.Closer - dialingConns int64 - copyingConns int64 - mx sync.RWMutex -} - -func newProxiedTCPHandler(c *client, dialer *bandit.BanditDialer, grabber dnsgrab.Server) *proxiedTCPHandler { - result := &proxiedTCPHandler{ - dialOut: dialer.DialContext, - client: c, - grabber: grabber, - mtu: c.mtu, - dialRequests: make(chan *dialRequest), - downstreamWriteWorker: newWorker(downstreamWriteBufferDepth), - upstreams: make(map[io.Closer]io.Closer), - } - go result.trackStats() - result.handleDials() - return result -} - -// dialing is very memory intensive because of the cryptography involved, so we limit the concurrency of dialing to keep our memory usage -// under control -func (h *proxiedTCPHandler) handleDials() { - for i := 0; i < maxConcurrentDials; i++ { - go h.handleDial() - } -} - -func (h *proxiedTCPHandler) handleDial() { - // MEMORY_OPTIMIZATION - locking to the OS thread seems to help keep Go from spawning more OS threads when cgo calls are blocked - runtime.LockOSThread() - - for req := range h.dialRequests { - upstream, err := h.dialOut(req.ctx, bandit.NetworkConnect, req.addr) - if err == nil { - req.upstream <- upstream - } else { - req.err <- err - } - } -} - -func (h *proxiedTCPHandler) Handle(_downstream net.Conn, addr *net.TCPAddr) error { - host, ok := h.grabber.ReverseLookup(addr.IP) - if !ok { - return log.Errorf("Invalid ip address %v, will not connect", addr.IP) - } - - ctx, cancelContext := context.WithTimeout(context.Background(), dialTimeout) - addrString := fmt.Sprintf("%v:%d", host, addr.Port) - - // MEMORY_OPTIMIZATION - dialing is very memory intensive because of the cryptography involved, so we limit the - // concurrency of dialing to keep our memory usage under control - req := &dialRequest{ - ctx: ctx, - addr: addrString, - upstream: make(chan net.Conn), - err: make(chan error), - } - var upstream net.Conn - var err error - - atomic.AddInt64(&h.dialingConns, 1) - h.dialRequests <- req - - select { - case upstream = <-req.upstream: - // okay - case err = <-req.err: - // error - } - atomic.AddInt64(&h.dialingConns, -1) - - if err != nil { - cancelContext() - return log.Errorf("Unable to dial %v: %v", addrString, err) - } - - downstream := newThreadLimitingTCPConn(_downstream, h.downstreamWriteWorker) - - h.mx.Lock() - h.upstreams[downstream] = upstream - h.mx.Unlock() - - go func() { - h.copy(downstream, upstream) - cancelContext() - }() - - return nil -} - -func (h *proxiedTCPHandler) copy(downstream net.Conn, upstream net.Conn) { - atomic.AddInt64(&h.copyingConns, 1) - defer atomic.AddInt64(&h.copyingConns, -1) - - defer downstream.Close() - defer upstream.Close() - defer func() { - h.mx.Lock() - delete(h.upstreams, downstream) - h.mx.Unlock() - }() - - // MEMORY_OPTIMIZATION - we don't pool these as pooling seems to create additional memory pressure somehow - bufOut := make([]byte, h.mtu) - bufIn := make([]byte, h.mtu) - - closeTimer := time.NewTimer(closeTimeout) - keepFresh := func(_ int) { - if !closeTimer.Stop() { - <-closeTimer.C - } - closeTimer.Reset(closeTimeout) - } - - // MEMORY_OPTIMIZATION - use a threadLimitingTCPConn to limit the number of goroutines that write to lwip - outErrCh, inErrCh := netx.BidiCopyWithOpts(upstream, downstream, &netx.CopyOpts{ - BufOut: bufOut, - BufIn: bufIn, - OnOut: keepFresh, - OnIn: keepFresh, - }) - - logError := func(err error) { - if err == nil { - return - } else if idletiming.IsIdled(upstream) { - log.Debug(err) - } else { - log.Error(err) - } - } - - var wg sync.WaitGroup - wg.Add(2) - - go func() { - defer wg.Done() - - outErr := <-outErrCh - logError(outErr) - select { - case inErr := <-inErrCh: - logError(inErr) - case <-closeTimer.C: - log.Trace("opposite direction idle for more than closeTimeout, close everything") - upstream.Close() - downstream.Close() - } - }() - - go func() { - defer wg.Done() - - inErr := <-inErrCh - logError(inErr) - select { - case outErr := <-outErrCh: - logError(outErr) - case <-closeTimer.C: - log.Trace("opposite direction idle for more than closeTimeout, close everything") - upstream.Close() - downstream.Close() - } - }() - - wg.Wait() -} - -func (h *proxiedTCPHandler) trackStats() { - for { - h.mx.RLock() - numUpstreams := len(h.upstreams) - h.mx.RUnlock() - statsLog.Debugf("TCP Conns Upstreams: %d Dialing: %d Copying: %d", numUpstreams, atomic.LoadInt64(&h.dialingConns), atomic.LoadInt64(&h.copyingConns)) - time.Sleep(1 * time.Second) - } -} diff --git a/ios/thread_limiting_conn.go b/ios/thread_limiting_conn.go deleted file mode 100644 index 09c58626c..000000000 --- a/ios/thread_limiting_conn.go +++ /dev/null @@ -1,98 +0,0 @@ -package ios - -import ( - "errors" - "net" - "runtime" - "sync" - - "github.com/eycorsican/go-tun2socks/core" - - "github.com/getlantern/safechannels" -) - -var ( - errWriteOnClosedConn = errors.New("write on closed threadLimitingTCPConn") -) - -type worker struct { - tasks chan func() -} - -func newWorker(bufferDepth int) *worker { - w := &worker{ - tasks: make(chan func(), bufferDepth), - } - go w.work() - return w -} - -func (w *worker) work() { - // MEMORY_OPTIMIZATION - locking to the OS thread seems to help keep Go from spawning more OS threads when cgo calls are blocked - runtime.LockOSThread() - - for task := range w.tasks { - task() - } -} - -type threadLimitingTCPConn struct { - net.Conn - writeWorker *worker - writeResult safechannels.IO - closeOnce sync.Once -} - -func newThreadLimitingTCPConn(wrapped net.Conn, writeWorker *worker) net.Conn { - return &threadLimitingTCPConn{ - Conn: wrapped, - writeWorker: writeWorker, - writeResult: safechannels.NewIO(1), - } -} - -func (c *threadLimitingTCPConn) Write(b []byte) (int, error) { - c.writeWorker.tasks <- func() { - c.writeResult.Write(c.Conn.Write(b)) - } - result, ok := <-c.writeResult.Read() - if !ok { - return 0, errWriteOnClosedConn - } - return result.N, result.Err -} - -func (c *threadLimitingTCPConn) Close() (err error) { - c.closeOnce.Do(func() { - c.writeResult.Close() - err = c.Conn.Close() - }) - return -} - -// Wrapped implements the interface netx.WrappedConn -func (c *threadLimitingTCPConn) Wrapped() net.Conn { - return c.Conn -} - -type threadLimitingUDPConn struct { - core.UDPConn - writeWorker *worker -} - -func newThreadLimitingUDPConn(wrapped core.UDPConn, writeWorker *worker) core.UDPConn { - return &threadLimitingUDPConn{ - UDPConn: wrapped, - writeWorker: writeWorker, - } -} - -func (c *threadLimitingUDPConn) WriteFrom(b []byte, addr *net.UDPAddr) (int, error) { - writeResult := make(chan *safechannels.IOResult) - c.writeWorker.tasks <- func() { - n, err := c.UDPConn.WriteFrom(b, addr) - writeResult <- &safechannels.IOResult{n, err} - } - result := <-writeResult - return result.N, result.Err -} diff --git a/ios/thread_limiting_conn_test.go b/ios/thread_limiting_conn_test.go deleted file mode 100644 index ef78b475e..000000000 --- a/ios/thread_limiting_conn_test.go +++ /dev/null @@ -1,54 +0,0 @@ -package ios - -import ( - "io" - "net" - "sync" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestThreadLimitingConn(t *testing.T) { - const msg = "echo me" - - l, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - - go func() { - for { - conn, err := l.Accept() - if err == nil { - go func() { - io.Copy(conn, conn) - }() - } - } - }() - - worker := newWorker(1) - - readers := 100 - var wg sync.WaitGroup - wg.Add(readers) - for i := 0; i < readers; i++ { - go func() { - defer wg.Done() - _conn, err := net.Dial("tcp", l.Addr().String()) - require.NoError(t, err) - conn := newThreadLimitingTCPConn(_conn, worker) - n, err := conn.Write([]byte(msg)) - require.Equal(t, n, len(msg)) - b := make([]byte, 1024) - n, err = conn.Read(b) - require.Equal(t, n, len(msg)) - require.Equal(t, msg, string(b[:n])) - - conn.Close() - _, err = conn.Write([]byte("extra")) - require.Equal(t, errWriteOnClosedConn, err) - }() - } - - wg.Wait() -} diff --git a/ios/udp.go b/ios/udp.go deleted file mode 100644 index 3c3b60fb3..000000000 --- a/ios/udp.go +++ /dev/null @@ -1,241 +0,0 @@ -package ios - -import ( - "net" - "sync" - "sync/atomic" - "time" - - "github.com/eycorsican/go-tun2socks/core" - - "github.com/getlantern/dnsgrab" - "github.com/getlantern/flashlight/v7/chained" -) - -// UDPDialer provides a mechanism for dialing outbound UDP connections that bypass the VPN. -// The returned UDPConn is not immediately ready for use, only once the UDPCallbacks receive -// OnDialSuccess is the UDPConn ready for use. -type UDPDialer interface { - Dial(host string, port int) UDPConn -} - -// UDPConn is a UDP connection that bypasses the VPN. It is backed by an NWConnection on the -// Swift side. -// -// See https://developer.apple.com/documentation/network/nwconnection. -type UDPConn interface { - // RegisterCallbacks registers lifecycle callbacks for the connection. Clients of the UDPConn - // must call this before trying to use WriteDatagram and ReceiveDatagram. - RegisterCallbacks(cb *UDPCallbacks) - - // WriteDatagram writes one datagram to the UDPConn. Any resulting error from the right will - // be reported to UDPCallbacks.OnError. - WriteDatagram([]byte) - - // ReceiveDatagram requests receipt of the next datagram from the UDPConn. Once the datagram is received, - // it's sent to UDPCallbacks.OnReceive. - ReceiveDatagram() - - // Close closes the UDPConn - Close() -} - -type UDPCallbacks struct { - h *directUDPHandler - downstream core.UDPConn - upstream UDPConn - target *net.UDPAddr - dialSucceeded chan interface{} - dialFailed chan interface{} - received chan interface{} - wrote chan interface{} -} - -// OnConn is called once a connection is successfully dialed -func (cb *UDPCallbacks) OnDialSucceeded() { - close(cb.dialSucceeded) -} - -func (cb *UDPCallbacks) OnError(err error) { - log.Errorf("Error communicating with %v: %v", cb.target, err) -} - -// OnClose is called when the connection is closed. -func (cb *UDPCallbacks) OnClose() { - cb.h.Lock() - delete(cb.h.upstreams, cb.downstream) - cb.h.Unlock() - cb.downstream.Close() -} - -func (cb *UDPCallbacks) OnReceive(dgram []byte) { - // Request receive of next datagram - cb.upstream.ReceiveDatagram() - - // Forward datagram downstream - _, writeErr := cb.downstream.WriteFrom(dgram, cb.target) - if writeErr != nil { - log.Errorf("Unable to write UDP packet downstream: %v", writeErr) - cb.upstream.Close() - } - - cb.received <- nil -} - -func (cb *UDPCallbacks) OnWritten() { - cb.wrote <- nil -} - -func (cb *UDPCallbacks) idleTiming() { - t := time.NewTimer(chained.IdleTimeout) - resetTimer := func() { - if !t.Stop() { - <-t.C - } - next := time.Duration(chained.IdleTimeout) - t.Reset(next) - } - - for { - select { - case <-cb.received: - resetTimer() - case <-cb.wrote: - resetTimer() - case <-t.C: - log.Debugf("Timing out idle connection to %v", cb.target) - cb.upstream.Close() // we don't close downstream because that'll happen automatically once upstream finishes closing - return - } - } -} - -// directUDPHandler implements UDPConnHandler from go-tun2socks by sending UDP traffic directly to -// the origin. It is loosely based on https://github.com/eycorsican/go-tun2socks/blob/master/proxy/socks/udp.go -type directUDPHandler struct { - sync.RWMutex - - client *client - dialer UDPDialer - grabber dnsgrab.Server - capturedDNSHost string - - downstreamWriteWorker *worker - upstreams map[core.UDPConn]UDPConn - dialingConns int64 -} - -func newDirectUDPHandler(client *client, dialer UDPDialer, grabber dnsgrab.Server, capturedDNSHost string) *directUDPHandler { - result := &directUDPHandler{ - client: client, - dialer: dialer, - capturedDNSHost: capturedDNSHost, - grabber: grabber, - downstreamWriteWorker: newWorker(100), - upstreams: make(map[core.UDPConn]UDPConn), - } - go result.trackStats() - return result -} - -func (h *directUDPHandler) Connect(downstream core.UDPConn, target *net.UDPAddr) error { - if target.IP.String() == h.capturedDNSHost && target.Port == 53 { - // Captured dns, handle internally with dnsgrab - return nil - } - - // Since UDP traffic is sent directly, do a reverse lookup of the IP and then resolve the UDP address - host, found := h.grabber.ReverseLookup(target.IP) - if !found { - return log.Errorf("Unknown IP %v, not connecting", target.IP) - } - if found { - ip, err := net.ResolveIPAddr("ip", host) - if err != nil { - return log.Errorf("Unable to resolve IP address for %v, not connecting: %v", host, err) - } - target.IP = ip.IP - } - - // Dial - atomic.AddInt64(&h.dialingConns, 1) - defer atomic.AddInt64(&h.dialingConns, -1) - - // note - the below convoluted flow is necessary because of limitations in what kind - // of APIs can be bound to Swift using gomobile. - upstream := h.dialer.Dial(target.IP.String(), target.Port) - - cb := &UDPCallbacks{ - h: h, - // MEMORY_OPTIMIZATION - use a threadLimitingUDPConn to limit the number of goroutines that are writing to LWIP - downstream: newThreadLimitingUDPConn(downstream, h.downstreamWriteWorker), - upstream: upstream, - target: target, - dialSucceeded: make(chan interface{}), - dialFailed: make(chan interface{}), - received: make(chan interface{}, 10), - wrote: make(chan interface{}, 10), - } - - upstream.RegisterCallbacks(cb) - - select { - case <-cb.dialFailed: - return log.Errorf("Failed to dial %v", target) - case <-cb.dialSucceeded: - h.Lock() - h.upstreams[downstream] = upstream - h.Unlock() - - // Request to receive first datagram - upstream.ReceiveDatagram() - case <-time.After(dialTimeout): - upstream.Close() - return log.Errorf("Timed out dialing %v", target) - } - - go cb.idleTiming() - - return nil -} - -func (h *directUDPHandler) ReceiveTo(downstream core.UDPConn, data []byte, addr *net.UDPAddr) error { - h.RLock() - upstream := h.upstreams[downstream] - h.RUnlock() - - if upstream == nil { - // if there's no upstream, that means this is a DNS query - return h.receiveDNS(downstream, data, addr) - } - - upstream.WriteDatagram(data) - return nil -} - -func (h *directUDPHandler) receiveDNS(downstream core.UDPConn, data []byte, addr *net.UDPAddr) error { - response, numAnswers, err := h.grabber.ProcessQuery(data) - if err != nil { - return log.Errorf("Unable to process dns query: %v", err) - } - - if numAnswers == 0 { - // nothing to write - return nil - } - - // MEMORY_OPTIMIZATION - use a threadLimitingUDPConn to limit the number of goroutines that are writing to LWIP - _, writeErr := newThreadLimitingUDPConn(downstream, h.downstreamWriteWorker).WriteFrom(response, addr) - return writeErr -} - -func (h *directUDPHandler) trackStats() { - for { - h.RLock() - activeConns := len(h.upstreams) - h.RUnlock() - - statsLog.Debugf("UDP Conns Active: %d Dialing: %d", activeConns, atomic.LoadInt64(&h.dialingConns)) - time.Sleep(1 * time.Second) - } -} diff --git a/pro/client/api.pb.go b/pro/client/api.pb.go deleted file mode 100644 index fc190ad01..000000000 --- a/pro/client/api.pb.go +++ /dev/null @@ -1,1066 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.31.0 -// protoc v4.23.4 -// source: pro/client/api.proto - -package client - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - structpb "google.golang.org/protobuf/types/known/structpb" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type Provider int32 - -const ( - Provider_PROVIDER_UNSET Provider = 0 - Provider_STRIPE Provider = 1 - Provider_FREEKASSA Provider = 2 -) - -// Enum value maps for Provider. -var ( - Provider_name = map[int32]string{ - 0: "PROVIDER_UNSET", - 1: "STRIPE", - 2: "FREEKASSA", - } - Provider_value = map[string]int32{ - "PROVIDER_UNSET": 0, - "STRIPE": 1, - "FREEKASSA": 2, - } -) - -func (x Provider) Enum() *Provider { - p := new(Provider) - *p = x - return p -} - -func (x Provider) String() string { - return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) -} - -func (Provider) Descriptor() protoreflect.EnumDescriptor { - return file_pro_client_api_proto_enumTypes[0].Descriptor() -} - -func (Provider) Type() protoreflect.EnumType { - return &file_pro_client_api_proto_enumTypes[0] -} - -func (x Provider) Number() protoreflect.EnumNumber { - return protoreflect.EnumNumber(x) -} - -// Deprecated: Use Provider.Descriptor instead. -func (Provider) EnumDescriptor() ([]byte, []int) { - return file_pro_client_api_proto_rawDescGZIP(), []int{0} -} - -type ProPlan struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` - BestValue bool `protobuf:"varint,3,opt,name=bestValue,proto3" json:"bestValue,omitempty"` - UsdPrice int64 `protobuf:"varint,4,opt,name=usdPrice,proto3" json:"usdPrice,omitempty"` - Price map[string]int64 `protobuf:"bytes,5,rep,name=price,proto3" json:"price,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` - ExpectedMonthlyPrice map[string]int64 `protobuf:"bytes,6,rep,name=expectedMonthlyPrice,proto3" json:"expectedMonthlyPrice,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` - TotalCostBilledOneTime string `protobuf:"bytes,7,opt,name=totalCostBilledOneTime,proto3" json:"totalCostBilledOneTime,omitempty"` - OneMonthCost string `protobuf:"bytes,8,opt,name=oneMonthCost,proto3" json:"oneMonthCost,omitempty"` - TotalCost string `protobuf:"bytes,9,opt,name=totalCost,proto3" json:"totalCost,omitempty"` - FormattedBonus string `protobuf:"bytes,10,opt,name=formattedBonus,proto3" json:"formattedBonus,omitempty"` - RenewalText string `protobuf:"bytes,11,opt,name=renewalText,proto3" json:"renewalText,omitempty"` -} - -func (x *ProPlan) Reset() { - *x = ProPlan{} - if protoimpl.UnsafeEnabled { - mi := &file_pro_client_api_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ProPlan) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ProPlan) ProtoMessage() {} - -func (x *ProPlan) ProtoReflect() protoreflect.Message { - mi := &file_pro_client_api_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ProPlan.ProtoReflect.Descriptor instead. -func (*ProPlan) Descriptor() ([]byte, []int) { - return file_pro_client_api_proto_rawDescGZIP(), []int{0} -} - -func (x *ProPlan) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *ProPlan) GetDescription() string { - if x != nil { - return x.Description - } - return "" -} - -func (x *ProPlan) GetBestValue() bool { - if x != nil { - return x.BestValue - } - return false -} - -func (x *ProPlan) GetUsdPrice() int64 { - if x != nil { - return x.UsdPrice - } - return 0 -} - -func (x *ProPlan) GetPrice() map[string]int64 { - if x != nil { - return x.Price - } - return nil -} - -func (x *ProPlan) GetExpectedMonthlyPrice() map[string]int64 { - if x != nil { - return x.ExpectedMonthlyPrice - } - return nil -} - -func (x *ProPlan) GetTotalCostBilledOneTime() string { - if x != nil { - return x.TotalCostBilledOneTime - } - return "" -} - -func (x *ProPlan) GetOneMonthCost() string { - if x != nil { - return x.OneMonthCost - } - return "" -} - -func (x *ProPlan) GetTotalCost() string { - if x != nil { - return x.TotalCost - } - return "" -} - -func (x *ProPlan) GetFormattedBonus() string { - if x != nil { - return x.FormattedBonus - } - return "" -} - -func (x *ProPlan) GetRenewalText() string { - if x != nil { - return x.RenewalText - } - return "" -} - -type PlansResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Plans []*ProPlan `protobuf:"bytes,1,rep,name=plans,proto3" json:"plans,omitempty"` -} - -func (x *PlansResponse) Reset() { - *x = PlansResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_pro_client_api_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PlansResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PlansResponse) ProtoMessage() {} - -func (x *PlansResponse) ProtoReflect() protoreflect.Message { - mi := &file_pro_client_api_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PlansResponse.ProtoReflect.Descriptor instead. -func (*PlansResponse) Descriptor() ([]byte, []int) { - return file_pro_client_api_proto_rawDescGZIP(), []int{1} -} - -func (x *PlansResponse) GetPlans() []*ProPlan { - if x != nil { - return x.Plans - } - return nil -} - -type ProPaymentProvider struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Data map[string]string `protobuf:"bytes,2,rep,name=data,proto3" json:"data,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` -} - -func (x *ProPaymentProvider) Reset() { - *x = ProPaymentProvider{} - if protoimpl.UnsafeEnabled { - mi := &file_pro_client_api_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ProPaymentProvider) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ProPaymentProvider) ProtoMessage() {} - -func (x *ProPaymentProvider) ProtoReflect() protoreflect.Message { - mi := &file_pro_client_api_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ProPaymentProvider.ProtoReflect.Descriptor instead. -func (*ProPaymentProvider) Descriptor() ([]byte, []int) { - return file_pro_client_api_proto_rawDescGZIP(), []int{2} -} - -func (x *ProPaymentProvider) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *ProPaymentProvider) GetData() map[string]string { - if x != nil { - return x.Data - } - return nil -} - -type ProPaymentMethod struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` - Providers []*ProPaymentProvider `protobuf:"bytes,2,rep,name=providers,proto3" json:"providers,omitempty"` -} - -func (x *ProPaymentMethod) Reset() { - *x = ProPaymentMethod{} - if protoimpl.UnsafeEnabled { - mi := &file_pro_client_api_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ProPaymentMethod) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ProPaymentMethod) ProtoMessage() {} - -func (x *ProPaymentMethod) ProtoReflect() protoreflect.Message { - mi := &file_pro_client_api_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ProPaymentMethod.ProtoReflect.Descriptor instead. -func (*ProPaymentMethod) Descriptor() ([]byte, []int) { - return file_pro_client_api_proto_rawDescGZIP(), []int{3} -} - -func (x *ProPaymentMethod) GetMethod() string { - if x != nil { - return x.Method - } - return "" -} - -func (x *ProPaymentMethod) GetProviders() []*ProPaymentProvider { - if x != nil { - return x.Providers - } - return nil -} - -type PaymentMethodsResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Providers map[string]*structpb.ListValue `protobuf:"bytes,1,rep,name=providers,proto3" json:"providers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Icons map[string]*structpb.ListValue `protobuf:"bytes,2,rep,name=icons,proto3" json:"icons,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` - Plans []*ProPlan `protobuf:"bytes,3,rep,name=plans,proto3" json:"plans,omitempty"` -} - -func (x *PaymentMethodsResponse) Reset() { - *x = PaymentMethodsResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_pro_client_api_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PaymentMethodsResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PaymentMethodsResponse) ProtoMessage() {} - -func (x *PaymentMethodsResponse) ProtoReflect() protoreflect.Message { - mi := &file_pro_client_api_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PaymentMethodsResponse.ProtoReflect.Descriptor instead. -func (*PaymentMethodsResponse) Descriptor() ([]byte, []int) { - return file_pro_client_api_proto_rawDescGZIP(), []int{4} -} - -func (x *PaymentMethodsResponse) GetProviders() map[string]*structpb.ListValue { - if x != nil { - return x.Providers - } - return nil -} - -func (x *PaymentMethodsResponse) GetIcons() map[string]*structpb.ListValue { - if x != nil { - return x.Icons - } - return nil -} - -func (x *PaymentMethodsResponse) GetPlans() []*ProPlan { - if x != nil { - return x.Plans - } - return nil -} - -type PaymentRedirectRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Plan string `protobuf:"bytes,1,opt,name=plan,proto3" json:"plan,omitempty"` - Provider string `protobuf:"bytes,2,opt,name=provider,proto3" json:"provider,omitempty"` - Currency string `protobuf:"bytes,3,opt,name=currency,proto3" json:"currency,omitempty"` - Email string `protobuf:"bytes,4,opt,name=email,proto3" json:"email,omitempty"` - DeviceName string `protobuf:"bytes,5,opt,name=deviceName,proto3" json:"deviceName,omitempty"` - CountryCode string `protobuf:"bytes,6,opt,name=countryCode,proto3" json:"countryCode,omitempty"` -} - -func (x *PaymentRedirectRequest) Reset() { - *x = PaymentRedirectRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_pro_client_api_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PaymentRedirectRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PaymentRedirectRequest) ProtoMessage() {} - -func (x *PaymentRedirectRequest) ProtoReflect() protoreflect.Message { - mi := &file_pro_client_api_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PaymentRedirectRequest.ProtoReflect.Descriptor instead. -func (*PaymentRedirectRequest) Descriptor() ([]byte, []int) { - return file_pro_client_api_proto_rawDescGZIP(), []int{5} -} - -func (x *PaymentRedirectRequest) GetPlan() string { - if x != nil { - return x.Plan - } - return "" -} - -func (x *PaymentRedirectRequest) GetProvider() string { - if x != nil { - return x.Provider - } - return "" -} - -func (x *PaymentRedirectRequest) GetCurrency() string { - if x != nil { - return x.Currency - } - return "" -} - -func (x *PaymentRedirectRequest) GetEmail() string { - if x != nil { - return x.Email - } - return "" -} - -func (x *PaymentRedirectRequest) GetDeviceName() string { - if x != nil { - return x.DeviceName - } - return "" -} - -func (x *PaymentRedirectRequest) GetCountryCode() string { - if x != nil { - return x.CountryCode - } - return "" -} - -type PaymentRedirectResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Redirect string `protobuf:"bytes,1,opt,name=redirect,proto3" json:"redirect,omitempty"` -} - -func (x *PaymentRedirectResponse) Reset() { - *x = PaymentRedirectResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_pro_client_api_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PaymentRedirectResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PaymentRedirectResponse) ProtoMessage() {} - -func (x *PaymentRedirectResponse) ProtoReflect() protoreflect.Message { - mi := &file_pro_client_api_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PaymentRedirectResponse.ProtoReflect.Descriptor instead. -func (*PaymentRedirectResponse) Descriptor() ([]byte, []int) { - return file_pro_client_api_proto_rawDescGZIP(), []int{6} -} - -func (x *PaymentRedirectResponse) GetRedirect() string { - if x != nil { - return x.Redirect - } - return "" -} - -type PurchaseRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Provider Provider `protobuf:"varint,1,opt,name=provider,proto3,enum=Provider" json:"provider,omitempty"` - Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` - Plan string `protobuf:"bytes,3,opt,name=plan,proto3" json:"plan,omitempty"` - CardNumber string `protobuf:"bytes,4,opt,name=cardNumber,proto3" json:"cardNumber,omitempty"` - ExpDate string `protobuf:"bytes,5,opt,name=expDate,proto3" json:"expDate,omitempty"` - Cvc string `protobuf:"bytes,6,opt,name=cvc,proto3" json:"cvc,omitempty"` - Currency string `protobuf:"bytes,7,opt,name=currency,proto3" json:"currency,omitempty"` - DeviceName string `protobuf:"bytes,8,opt,name=deviceName,proto3" json:"deviceName,omitempty"` - StripePublicKey string `protobuf:"bytes,9,opt,name=stripePublicKey,proto3" json:"stripePublicKey,omitempty"` - StripeEmail string `protobuf:"bytes,10,opt,name=stripeEmail,proto3" json:"stripeEmail,omitempty"` - StripeToken string `protobuf:"bytes,11,opt,name=stripeToken,proto3" json:"stripeToken,omitempty"` - Token string `protobuf:"bytes,12,opt,name=token,proto3" json:"token,omitempty"` - ResellerCode string `protobuf:"bytes,13,opt,name=resellerCode,proto3" json:"resellerCode,omitempty"` -} - -func (x *PurchaseRequest) Reset() { - *x = PurchaseRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_pro_client_api_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PurchaseRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PurchaseRequest) ProtoMessage() {} - -func (x *PurchaseRequest) ProtoReflect() protoreflect.Message { - mi := &file_pro_client_api_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PurchaseRequest.ProtoReflect.Descriptor instead. -func (*PurchaseRequest) Descriptor() ([]byte, []int) { - return file_pro_client_api_proto_rawDescGZIP(), []int{7} -} - -func (x *PurchaseRequest) GetProvider() Provider { - if x != nil { - return x.Provider - } - return Provider_PROVIDER_UNSET -} - -func (x *PurchaseRequest) GetEmail() string { - if x != nil { - return x.Email - } - return "" -} - -func (x *PurchaseRequest) GetPlan() string { - if x != nil { - return x.Plan - } - return "" -} - -func (x *PurchaseRequest) GetCardNumber() string { - if x != nil { - return x.CardNumber - } - return "" -} - -func (x *PurchaseRequest) GetExpDate() string { - if x != nil { - return x.ExpDate - } - return "" -} - -func (x *PurchaseRequest) GetCvc() string { - if x != nil { - return x.Cvc - } - return "" -} - -func (x *PurchaseRequest) GetCurrency() string { - if x != nil { - return x.Currency - } - return "" -} - -func (x *PurchaseRequest) GetDeviceName() string { - if x != nil { - return x.DeviceName - } - return "" -} - -func (x *PurchaseRequest) GetStripePublicKey() string { - if x != nil { - return x.StripePublicKey - } - return "" -} - -func (x *PurchaseRequest) GetStripeEmail() string { - if x != nil { - return x.StripeEmail - } - return "" -} - -func (x *PurchaseRequest) GetStripeToken() string { - if x != nil { - return x.StripeToken - } - return "" -} - -func (x *PurchaseRequest) GetToken() string { - if x != nil { - return x.Token - } - return "" -} - -func (x *PurchaseRequest) GetResellerCode() string { - if x != nil { - return x.ResellerCode - } - return "" -} - -type PurchaseResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"` -} - -func (x *PurchaseResponse) Reset() { - *x = PurchaseResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_pro_client_api_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *PurchaseResponse) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*PurchaseResponse) ProtoMessage() {} - -func (x *PurchaseResponse) ProtoReflect() protoreflect.Message { - mi := &file_pro_client_api_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use PurchaseResponse.ProtoReflect.Descriptor instead. -func (*PurchaseResponse) Descriptor() ([]byte, []int) { - return file_pro_client_api_proto_rawDescGZIP(), []int{8} -} - -func (x *PurchaseResponse) GetSuccess() bool { - if x != nil { - return x.Success - } - return false -} - -var File_pro_client_api_proto protoreflect.FileDescriptor - -var file_pro_client_api_proto_rawDesc = []byte{ - 0x0a, 0x14, 0x70, 0x72, 0x6f, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x61, 0x70, 0x69, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbf, 0x04, 0x0a, 0x07, 0x50, 0x72, 0x6f, 0x50, 0x6c, 0x61, 0x6e, - 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, - 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x62, 0x65, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x62, 0x65, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x64, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x08, 0x75, 0x73, 0x64, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, 0x29, 0x0a, 0x05, - 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x50, 0x72, - 0x6f, 0x50, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x56, 0x0a, 0x14, 0x65, 0x78, 0x70, 0x65, 0x63, - 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, 0x63, 0x65, 0x18, - 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x50, 0x72, 0x6f, 0x50, 0x6c, 0x61, 0x6e, 0x2e, - 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x50, - 0x72, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x65, 0x78, 0x70, 0x65, 0x63, - 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, 0x50, 0x72, 0x69, 0x63, 0x65, 0x12, - 0x36, 0x0a, 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, - 0x65, 0x64, 0x4f, 0x6e, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x16, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x42, 0x69, 0x6c, 0x6c, 0x65, 0x64, - 0x4f, 0x6e, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x6e, 0x65, 0x4d, 0x6f, - 0x6e, 0x74, 0x68, 0x43, 0x6f, 0x73, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, - 0x6e, 0x65, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, - 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x66, 0x6f, 0x72, - 0x6d, 0x61, 0x74, 0x74, 0x65, 0x64, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x74, 0x65, 0x64, 0x42, 0x6f, 0x6e, 0x75, - 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x54, 0x65, 0x78, 0x74, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x54, - 0x65, 0x78, 0x74, 0x1a, 0x38, 0x0a, 0x0a, 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x47, 0x0a, - 0x19, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x6e, 0x74, 0x68, 0x6c, 0x79, - 0x50, 0x72, 0x69, 0x63, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x2f, 0x0a, 0x0d, 0x50, 0x6c, 0x61, 0x6e, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1e, 0x0a, 0x05, 0x70, 0x6c, 0x61, 0x6e, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x50, 0x72, 0x6f, 0x50, 0x6c, 0x61, 0x6e, - 0x52, 0x05, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x22, 0x94, 0x01, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x12, - 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x1d, 0x2e, 0x50, 0x72, 0x6f, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, - 0x76, 0x69, 0x64, 0x65, 0x72, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, - 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x44, 0x61, 0x74, 0x61, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x5d, - 0x0a, 0x10, 0x50, 0x72, 0x6f, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, - 0x6f, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x31, 0x0a, 0x09, 0x70, 0x72, - 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, - 0x50, 0x72, 0x6f, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, - 0x65, 0x72, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x22, 0xe8, 0x02, - 0x0a, 0x16, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44, 0x0a, 0x09, 0x70, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x73, 0x12, 0x38, - 0x0a, 0x05, 0x69, 0x63, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x63, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x05, 0x69, 0x63, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x0a, 0x05, 0x70, 0x6c, 0x61, 0x6e, - 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x08, 0x2e, 0x50, 0x72, 0x6f, 0x50, 0x6c, 0x61, - 0x6e, 0x52, 0x05, 0x70, 0x6c, 0x61, 0x6e, 0x73, 0x1a, 0x58, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, - 0x38, 0x01, 0x1a, 0x54, 0x0a, 0x0a, 0x49, 0x63, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xbc, 0x01, 0x0a, 0x16, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, - 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4e, - 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, - 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, - 0x43, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x22, 0x35, 0x0a, 0x17, 0x50, 0x61, 0x79, 0x6d, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0x92, - 0x03, 0x0a, 0x0f, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x25, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x09, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x52, - 0x08, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x6d, 0x61, - 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x12, - 0x12, 0x0a, 0x04, 0x70, 0x6c, 0x61, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, - 0x6c, 0x61, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, - 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x61, 0x72, 0x64, 0x4e, 0x75, 0x6d, - 0x62, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x44, 0x61, 0x74, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x78, 0x70, 0x44, 0x61, 0x74, 0x65, 0x12, 0x10, 0x0a, - 0x03, 0x63, 0x76, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x63, 0x76, 0x63, 0x12, - 0x1a, 0x0a, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x64, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x0a, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x73, - 0x74, 0x72, 0x69, 0x70, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x74, 0x72, 0x69, 0x70, 0x65, 0x50, 0x75, 0x62, 0x6c, - 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x70, 0x65, 0x45, - 0x6d, 0x61, 0x69, 0x6c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, - 0x70, 0x65, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x20, 0x0a, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x70, - 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x74, - 0x72, 0x69, 0x70, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, - 0x22, 0x0a, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x18, - 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x6c, 0x6c, 0x65, 0x72, 0x43, - 0x6f, 0x64, 0x65, 0x22, 0x2c, 0x0a, 0x10, 0x50, 0x75, 0x72, 0x63, 0x68, 0x61, 0x73, 0x65, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x2a, 0x39, 0x0a, 0x08, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, - 0x0e, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x52, 0x5f, 0x55, 0x4e, 0x53, 0x45, 0x54, 0x10, - 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x52, 0x49, 0x50, 0x45, 0x10, 0x01, 0x12, 0x0d, 0x0a, - 0x09, 0x46, 0x52, 0x45, 0x45, 0x4b, 0x41, 0x53, 0x53, 0x41, 0x10, 0x02, 0x42, 0x2d, 0x5a, 0x2b, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x65, 0x74, 0x6c, 0x61, - 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x2f, 0x66, 0x6c, 0x61, 0x73, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, - 0x2f, 0x70, 0x72, 0x6f, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, -} - -var ( - file_pro_client_api_proto_rawDescOnce sync.Once - file_pro_client_api_proto_rawDescData = file_pro_client_api_proto_rawDesc -) - -func file_pro_client_api_proto_rawDescGZIP() []byte { - file_pro_client_api_proto_rawDescOnce.Do(func() { - file_pro_client_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_pro_client_api_proto_rawDescData) - }) - return file_pro_client_api_proto_rawDescData -} - -var file_pro_client_api_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_pro_client_api_proto_msgTypes = make([]protoimpl.MessageInfo, 14) -var file_pro_client_api_proto_goTypes = []interface{}{ - (Provider)(0), // 0: Provider - (*ProPlan)(nil), // 1: ProPlan - (*PlansResponse)(nil), // 2: PlansResponse - (*ProPaymentProvider)(nil), // 3: ProPaymentProvider - (*ProPaymentMethod)(nil), // 4: ProPaymentMethod - (*PaymentMethodsResponse)(nil), // 5: PaymentMethodsResponse - (*PaymentRedirectRequest)(nil), // 6: PaymentRedirectRequest - (*PaymentRedirectResponse)(nil), // 7: PaymentRedirectResponse - (*PurchaseRequest)(nil), // 8: PurchaseRequest - (*PurchaseResponse)(nil), // 9: PurchaseResponse - nil, // 10: ProPlan.PriceEntry - nil, // 11: ProPlan.ExpectedMonthlyPriceEntry - nil, // 12: ProPaymentProvider.DataEntry - nil, // 13: PaymentMethodsResponse.ProvidersEntry - nil, // 14: PaymentMethodsResponse.IconsEntry - (*structpb.ListValue)(nil), // 15: google.protobuf.ListValue -} -var file_pro_client_api_proto_depIdxs = []int32{ - 10, // 0: ProPlan.price:type_name -> ProPlan.PriceEntry - 11, // 1: ProPlan.expectedMonthlyPrice:type_name -> ProPlan.ExpectedMonthlyPriceEntry - 1, // 2: PlansResponse.plans:type_name -> ProPlan - 12, // 3: ProPaymentProvider.data:type_name -> ProPaymentProvider.DataEntry - 3, // 4: ProPaymentMethod.providers:type_name -> ProPaymentProvider - 13, // 5: PaymentMethodsResponse.providers:type_name -> PaymentMethodsResponse.ProvidersEntry - 14, // 6: PaymentMethodsResponse.icons:type_name -> PaymentMethodsResponse.IconsEntry - 1, // 7: PaymentMethodsResponse.plans:type_name -> ProPlan - 0, // 8: PurchaseRequest.provider:type_name -> Provider - 15, // 9: PaymentMethodsResponse.ProvidersEntry.value:type_name -> google.protobuf.ListValue - 15, // 10: PaymentMethodsResponse.IconsEntry.value:type_name -> google.protobuf.ListValue - 11, // [11:11] is the sub-list for method output_type - 11, // [11:11] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name -} - -func init() { file_pro_client_api_proto_init() } -func file_pro_client_api_proto_init() { - if File_pro_client_api_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_pro_client_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProPlan); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_pro_client_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PlansResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_pro_client_api_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProPaymentProvider); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_pro_client_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ProPaymentMethod); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_pro_client_api_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PaymentMethodsResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_pro_client_api_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PaymentRedirectRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_pro_client_api_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PaymentRedirectResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_pro_client_api_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PurchaseRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_pro_client_api_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PurchaseResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_pro_client_api_proto_rawDesc, - NumEnums: 1, - NumMessages: 14, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_pro_client_api_proto_goTypes, - DependencyIndexes: file_pro_client_api_proto_depIdxs, - EnumInfos: file_pro_client_api_proto_enumTypes, - MessageInfos: file_pro_client_api_proto_msgTypes, - }.Build() - File_pro_client_api_proto = out.File - file_pro_client_api_proto_rawDesc = nil - file_pro_client_api_proto_goTypes = nil - file_pro_client_api_proto_depIdxs = nil -} diff --git a/pro/client/api.proto b/pro/client/api.proto deleted file mode 100644 index 2ecdac44d..000000000 --- a/pro/client/api.proto +++ /dev/null @@ -1,76 +0,0 @@ -syntax = "proto3"; -import "google/protobuf/struct.proto"; -option go_package = "github.com/getlantern/flashlight/pro/client"; - -message ProPlan { - string id = 1; - string description = 2; - bool bestValue = 3; - int64 usdPrice = 4; - map price = 5; - map expectedMonthlyPrice = 6; - string totalCostBilledOneTime = 7; - string oneMonthCost = 8; - string totalCost = 9; - string formattedBonus = 10; - string renewalText = 11; -} - -message PlansResponse { - repeated ProPlan plans = 1; -} - -message ProPaymentProvider { - string name = 1; - map data = 2; -} - -message ProPaymentMethod { - string method = 1; - repeated ProPaymentProvider providers = 2; -} - -message PaymentMethodsResponse { - map providers = 1; - map icons = 2; - repeated ProPlan plans = 3; -} - -enum Provider { - PROVIDER_UNSET = 0; - STRIPE = 1; - FREEKASSA = 2; -} - -message PaymentRedirectRequest { - string plan = 1; - string provider = 2; - string currency = 3; - string email = 4; - string deviceName = 5; - string countryCode = 6; -} - -message PaymentRedirectResponse { - string redirect = 1; -} - -message PurchaseRequest { - Provider provider = 1; - string email = 2; - string plan = 3; - string cardNumber = 4; - string expDate = 5; - string cvc = 6; - string currency = 7; - string deviceName = 8; - string stripePublicKey = 9; - string stripeEmail = 10; - string stripeToken = 11; - string token = 12; - string resellerCode = 13; -} - -message PurchaseResponse { - bool success = 1; -} diff --git a/pro/client/client.go b/pro/client/client.go deleted file mode 100644 index bf6cd867c..000000000 --- a/pro/client/client.go +++ /dev/null @@ -1,436 +0,0 @@ -package client - -import ( - "bytes" - "encoding/json" - "errors" - "io/ioutil" - "math" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/getlantern/flashlight/v7/common" - "github.com/getlantern/golog" -) - -var ( - log = golog.LoggerFor("pro-server-client") - - defaultTimeout = time.Second * 30 - maxRetries = 4 - retryBaseTime = time.Millisecond * 100 -) - -var ( - ErrAPIUnavailable = errors.New("API unavailable.") -) - -type baseResponse interface { - status() string - error() string -} - -type BaseResponse struct { - Status string `json:"status"` - Error string `json:"error"` - ErrorId string `json:"errorId"` -} - -func (resp BaseResponse) status() string { - return resp.Status -} - -func (resp BaseResponse) error() string { - return resp.Error -} - -type UserDataResponse struct { - BaseResponse - User `json:",inline"` -} - -type LinkResponse struct { - BaseResponse - UserID int `json:"userID"` - ProToken string `json:"token"` -} - -type LinkCodeResponse struct { - BaseResponse - Code string - ExpireAt int64 -} - -type Client struct { - httpClient *http.Client - preparePro func(*http.Request, common.UserConfig) -} - -// NewClient creates a new pro client. -func NewClient(httpClient *http.Client, preparePro func(r *http.Request, uc common.UserConfig)) *Client { - if httpClient == nil { - httpClient = &http.Client{ - Timeout: defaultTimeout, - } - } - return &Client{httpClient: httpClient, preparePro: preparePro} -} - -// UserCreate creates an user without asking for any payment. -func (c *Client) UserCreate(user common.UserConfig) (res *UserDataResponse, err error) { - body := strings.NewReader(url.Values{"locale": {user.GetLanguage()}}.Encode()) - req, err := http.NewRequest(http.MethodPost, "https://"+common.ProAPIHost+"/user-create", body) - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - payload, err := c.do(user, req) - if err != nil { - return nil, err - } - err = json.Unmarshal(payload, &res) - return -} - -// UserData Returns all user data, including payments, referrals and all -// available fields. -func (c *Client) UserData(user common.UserConfig) (*UserDataResponse, error) { - query := url.Values{ - "timeout": {"10"}, - "locale": {user.GetLanguage()}, - } - - resp := &UserDataResponse{} - if err := c.execute(user, http.MethodGet, "user-data", query, resp); err != nil { - log.Errorf("Failed to get user data: %v", err) - return nil, err - } - - return resp, nil -} - -type plansResponse struct { - BaseResponse - *PlansResponse `json:",inline"` -} - -// Plans returns a list of Pro plans -func (c *Client) Plans(user common.UserConfig) (*plansResponse, error) { - query := url.Values{ - "locale": {user.GetLanguage()}, - } - - resp := &plansResponse{PlansResponse: &PlansResponse{}} - if err := c.execute(user, http.MethodGet, "plans", query, resp); err != nil { - log.Errorf("Failed to fetch plans: %v", err) - return nil, err - } - - return resp, nil -} - -type paymentRedirectResponse struct { - BaseResponse - *PaymentRedirectResponse `json:",inline"` -} - -// PaymentRedirect is called when the continue to payment button is clicked and returns a redirect URL -func (c *Client) PaymentRedirect(user common.UserConfig, req *PaymentRedirectRequest) (*paymentRedirectResponse, error) { - query := url.Values{ - "countryCode": {req.CountryCode}, - "deviceName": {req.DeviceName}, - "email": {req.Email}, - "plan": {req.Plan}, - "provider": {req.Provider}, - } - - b, _ := json.Marshal(user) - log.Debugf("User config is %v", string(b)) - - resp := &paymentRedirectResponse{PaymentRedirectResponse: &PaymentRedirectResponse{}} - if err := c.execute(user, http.MethodGet, "payment-redirect", query, resp); err != nil { - log.Errorf("Failed to fetch payment redirect: %v", err) - return nil, err - } - log.Debugf("Redirect is %s", resp.Redirect) - return resp, nil -} - -type paymentMethodsResponse struct { - *PaymentMethodsResponse `json:",inline"` - BaseResponse -} - -// PaymentMethodsV3 returns a list of payment options available to the given user -func (c *Client) PaymentMethodsV3(user common.UserConfig) (*paymentMethodsResponse, error) { - query := url.Values{ - "locale": {user.GetLanguage()}, - } - - resp := &paymentMethodsResponse{PaymentMethodsResponse: &PaymentMethodsResponse{}} - if err := c.execute(user, http.MethodGet, "plans-v3", query, resp); err != nil { - log.Errorf("Failed to fetch payment methods: %v", err) - return nil, err - } - return resp, nil -} - -// PaymentMethodsV3 returns a list of payment, plans and icons options available to the given user -func (c *Client) PaymentMethodsV4(user common.UserConfig) (*paymentMethodsResponse, error) { - query := url.Values{ - "locale": {user.GetLanguage()}, - } - - resp := &paymentMethodsResponse{PaymentMethodsResponse: &PaymentMethodsResponse{}} - if err := c.execute(user, http.MethodGet, "plans-v4", query, resp); err != nil { - log.Errorf("Failed to fetch payment methods-v4: %v", err) - return nil, err - } - return resp, nil -} - -// RecoverProAccount attempts to recover an existing Pro account linked to this email address and device ID -func (c *Client) RecoverProAccount(user common.UserConfig, emailAddress string) (*LinkResponse, error) { - query := url.Values{ - "email": {emailAddress}, - "locale": {user.GetLanguage()}, - } - - resp := &LinkResponse{} - if err := c.execute(user, http.MethodPost, "user-recover", query, resp); err != nil { - log.Errorf("Failed to recover pro user: %v", err) - return nil, err - } - - return resp, nil -} - -// EmailExists checks whether a Pro account exists with the given email address -func (c *Client) EmailExists(user common.UserConfig, emailAddress string) error { - query := url.Values{ - "email": {emailAddress}, - } - - resp := &BaseResponse{} - if err := c.execute(user, http.MethodGet, "email-exists", query, resp); err != nil { - log.Errorf("Failed to check if email exists: %v", err) - return err - } - - return nil -} - -// RequestRecoveryEmail requests an account recovery email for linking to an existing pro account -func (c *Client) RequestRecoveryEmail(user common.UserConfig, deviceName, emailAddress string) (err error) { - query := url.Values{ - "email": {emailAddress}, - "deviceName": {deviceName}, - "locale": {user.GetLanguage()}, - } - - resp := &BaseResponse{} - if err := c.execute(user, http.MethodPost, "user-link-request", query, resp); err != nil { - log.Errorf("Failed to request a recovery code: %v", err) - return err - } - - return nil -} - -// ValidateRecoveryCode validates the given recovery code and finishes linking the device, returning the user_id and pro_token for the account. -func (c *Client) ValidateRecoveryCode(user common.UserConfig, code string) (*LinkResponse, error) { - query := url.Values{ - "code": {code}, - "locale": {user.GetLanguage()}, - } - - resp := &LinkResponse{} - if err := c.execute(user, http.MethodPost, "user-link-validate", query, resp); err != nil { - log.Errorf("Failed to validate recovery code: %v", err) - return nil, err - } - - return resp, nil -} - -// RequestDeviceLinkingCode requests a new device linking code to allow linking the current device to a pro account via an existing pro device. -func (c *Client) RequestDeviceLinkingCode(user common.UserConfig, deviceName string) (*LinkCodeResponse, error) { - query := url.Values{ - "deviceName": {deviceName}, - "locale": {user.GetLanguage()}, - } - - resp := &LinkCodeResponse{} - if err := c.execute(user, http.MethodPost, "link-code-request", query, resp); err != nil { - log.Errorf("Failed to get link code: %v", err) - return nil, err - } - - return resp, nil -} - -// LinkCodeApprove approves a device linking code when requesting to use a device with a Pro account -func (c *Client) LinkCodeApprove(user common.UserConfig, code string) (*BaseResponse, error) { - query := url.Values{ - "code": {code}, - "locale": {user.GetLanguage()}, - } - - var resp BaseResponse - if err := c.execute(user, http.MethodPost, "link-code-approve", query, &resp); err != nil { - log.Errorf("Failed to approve link code: %v", err) - return nil, err - } - - return &resp, nil -} - -// DeviceRemove removes the device with the given ID from a user's Pro account -func (c *Client) DeviceRemove(user common.UserConfig, deviceID string) (*LinkResponse, error) { - query := url.Values{ - "deviceID": {deviceID}, - "locale": {user.GetLanguage()}, - } - - var resp LinkResponse - if err := c.execute(user, http.MethodPost, "user-link-remove", query, &resp); err != nil { - log.Errorf("Failed to remove link code: %v", err) - return nil, err - } - - return &resp, nil -} - -// ValidateDeviceLinkingCode validates a device linking code to allow linking the current device to a pro account via an existing pro device. -func (c *Client) ValidateDeviceLinkingCode(user common.UserConfig, deviceName, code string) (*LinkResponse, error) { - query := url.Values{ - "code": {code}, - "deviceName": {deviceName}, - "locale": {user.GetLanguage()}, - } - - resp := &LinkResponse{} - if err := c.execute(user, http.MethodPost, "link-code-redeem", query, resp); err != nil { - log.Errorf("Failed to validate link code: %v", err) - return nil, err - } - - return resp, nil -} - -// MigrateDeviceID migrates from the old device ID scheme to the new -func (c *Client) MigrateDeviceID(user common.UserConfig, oldDeviceID string) error { - query := url.Values{ - "oldDeviceID": {oldDeviceID}, - } - - resp := &BaseResponse{} - return c.execute(user, http.MethodPost, "migrate-device-id", query, resp) -} - -// RedeemResellerCode redeems a reseller code for the given user -// -// Note: In reality, the response for this route from pro-server is not -// BaseResponse but of this type -// https://github.com/getlantern/pro-server-neu/blob/34bcdc042e983bf9504014aa066bba6bdedcebdb/handlers/purchase.go#L201. -// That being said, we don't really care about the response from pro-server -// here. We just wanna know if it succeeded or failed, which is encapsulated in the fields of BaseResponse. -func (c *Client) RedeemResellerCode(user common.UserConfig, emailAddress, resellerCode, deviceName, currency string) (*BaseResponse, error) { - query := url.Values{ - "email": {emailAddress}, - "resellerCode": {resellerCode}, - "idempotencyKey": {strconv.FormatInt(time.Now().UnixMilli(), 10)}, - "currency": {currency}, - "deviceName": {deviceName}, - "provider": {"reseller-code"}, - } - - resp := &BaseResponse{} - if err := c.execute(user, http.MethodPost, "purchase", query, resp); err != nil { - log.Errorf("Failed to redeem reseller code: %v", err) - return nil, err - } - - return resp, nil -} - -func (c *Client) do(user common.UserConfig, req *http.Request) ([]byte, error) { - var buf []byte - if req.Body != nil { - var err error - buf, err = ioutil.ReadAll(req.Body) - if err != nil { - return nil, err - } - } - - c.preparePro(req, user) - - for i := 0; i < maxRetries; i++ { - client := c.httpClient - log.Debugf("%s %s", req.Method, req.URL) - if len(buf) > 0 { - req.Body = ioutil.NopCloser(bytes.NewBuffer(buf)) - } - - res, err := client.Do(req) - if err == nil { - defer res.Body.Close() - switch res.StatusCode { - case 200: - body, err := ioutil.ReadAll(res.Body) - return body, err - case 202: - log.Debugf("Received 202, retrying idempotent operation immediately.") - continue - default: - body, err := ioutil.ReadAll(res.Body) - if err == nil { - log.Debugf("Expecting 200, got: %d, body: %v", res.StatusCode, string(body)) - } else { - log.Debugf("Expecting 200, got: %d, could not get body: %v", res.StatusCode, err) - } - } - } else { - log.Debugf("Do: %v, res: %v", err, res) - } - - retryTime := time.Duration(math.Pow(2, float64(i))) * retryBaseTime - log.Debugf("failed, waiting %v to retry.", retryTime) - time.Sleep(retryTime) - } - return nil, ErrAPIUnavailable -} - -func (c *Client) execute(user common.UserConfig, method, path string, query url.Values, resp baseResponse) error { - req, err := http.NewRequest(method, "https://"+common.ProAPIHost+"/"+path, nil) - if err != nil { - return err - } - - req.Header.Set("Referer", "http://localhost:37457/") - - query["locale"] = []string{user.GetLanguage()} - req.URL.RawQuery = query.Encode() - - payload, err := c.do(user, req) - if err != nil { - return err - } - - err = json.Unmarshal(payload, &resp) - if err != nil { - return err - } - - if resp.status() == "error" { - return errors.New(resp.error()) - } - - return nil -} diff --git a/pro/client/client_test.go b/pro/client/client_test.go deleted file mode 100644 index dfb29e391..000000000 --- a/pro/client/client_test.go +++ /dev/null @@ -1,158 +0,0 @@ -package client - -import ( - "fmt" - "net/http" - "testing" - "time" - - "github.com/pborman/uuid" - "github.com/stretchr/testify/assert" - - "github.com/getlantern/flashlight/v7/common" -) - -func generateDeviceId() string { - return uuid.New() -} - -func generateUser() *common.UserConfigData { - return common.NewUserConfigData(common.DefaultAppName, generateDeviceId(), 35, "aasfge", nil, "en-US") -} - -func init() { - common.ForceStaging() -} - -func createClient() *Client { - return NewClient(nil, func(req *http.Request, uc common.UserConfig) { - common.AddCommonHeaders(uc, req) - }) -} - -func TestCreateUser(t *testing.T) { - user := generateUser() - - res, err := createClient().UserCreate(user) - if !assert.NoError(t, err) { - return - } - - assert.True(t, res.User.ID != 0) - assert.True(t, res.User.Expiration == 0) - assert.True(t, res.User.Token != "") - assert.True(t, res.User.Code != "") - assert.True(t, res.User.Referral == res.User.Code) -} - -func TestGetUserData(t *testing.T) { - user := generateUser() - res, err := createClient().UserCreate(user) - if !assert.NoError(t, err) { - return - } - user.UserID = res.User.ID - user.Token = res.User.Token - - // fetch this user's info with a new client - res, err = createClient().UserData(user) - if assert.NoError(t, err) { - assert.True(t, res.User.ID != 0) - assert.Equal(t, res.User.ID, user.UserID) - } -} - -func TestGetPlans(t *testing.T) { - user := generateUser() - res, err := createClient().UserCreate(user) - if !assert.NoError(t, err) { - return - } - user.UserID = res.User.ID - user.Token = res.User.Token - - plansRes, err := createClient().Plans(user) - if !assert.NoError(t, err) { - return - } - fmt.Println(plansRes.PlansResponse.Plans) - assert.True(t, len(plansRes.PlansResponse.Plans) > 0) -} - -func TestPaymentMethodsV4(t *testing.T) { - user := generateUser() - res, err := createClient().UserCreate(user) - if !assert.NoError(t, err) { - return - } - user.UserID = res.User.ID - user.Token = res.User.Token - - paymentResp, err := createClient().PaymentMethodsV4(user) - if !assert.NoError(t, err) { - return - } - fmt.Println(paymentResp.PaymentMethodsResponse) - assert.True(t, len(paymentResp.PaymentMethodsResponse.Icons) > 0) - assert.True(t, len(paymentResp.PaymentMethodsResponse.Plans) > 0) - assert.True(t, len(paymentResp.PaymentMethodsResponse.Providers) > 0) -} - -func TestUserDataMissing(t *testing.T) { - user := generateUser() - - _, err := createClient().UserData(user) - assert.Error(t, err) -} - -func TestUserDataWrong(t *testing.T) { - user := generateUser() - user.UserID = -1 - user.Token = "nonsense" - - _, err := createClient().UserData(user) - if assert.Error(t, err) { - assert.Contains(t, err.Error(), "Not authorized") - } -} - -func TestRequestDeviceLinkingCode(t *testing.T) { - user := generateUser() - res, err := createClient().UserCreate(user) - if !assert.NoError(t, err) { - return - } - user.UserID = res.User.ID - user.Token = res.User.Token - - lcr, err := createClient().RequestDeviceLinkingCode(user, "Test Device") - if assert.NoError(t, err) { - assert.NotEmpty(t, lcr.Code) - assert.True(t, time.Unix(lcr.ExpireAt, 0).After(time.Now())) - } -} - -func TestCreateUniqueUsers(t *testing.T) { - userA := generateUser() - res, err := createClient().UserCreate(userA) - if !assert.NoError(t, err) { - return - } - assert.True(t, res.User.ID != 0) - assert.True(t, res.User.Token != "") - userA.UserID = res.User.ID - userA.Token = res.User.Token - - userB := generateUser() - res, err = createClient().UserCreate(userB) - if !assert.NoError(t, err) { - return - } - assert.True(t, res.User.ID != 0) - assert.True(t, res.User.Token != "") - userB.UserID = res.User.ID - userB.Token = res.User.Token - - assert.NotEqual(t, userA.UserID, userB.UserID) - assert.NotEqual(t, userA.Token, userB.Token) -} diff --git a/pro/client/user.go b/pro/client/user.go deleted file mode 100644 index 6b101e2f1..000000000 --- a/pro/client/user.go +++ /dev/null @@ -1,26 +0,0 @@ -package client - -type Device struct { - Id string `json:"id"` - Name string `json:"name"` - Created int64 `json:"created"` -} - -type Auth struct { - ID int64 `json:"userId"` - Token string `json:"token"` -} - -type User struct { - Auth `json:",inline"` - Email string `json:"email"` - PhoneNumber string `json:"telephone"` - UserStatus string `json:"userStatus"` - Locale string `json:"locale"` - Expiration int64 `json:"expiration"` - Devices []Device `json:"devices"` - Code string `json:"code"` - ExpireAt int64 `json:"expireAt"` - Referral string `json:"referral"` - YinbiEnabled bool `json:"yinbiEnabled"` -} diff --git a/pro/http.go b/pro/http.go deleted file mode 100644 index ce765cd5b..000000000 --- a/pro/http.go +++ /dev/null @@ -1,29 +0,0 @@ -package pro - -import ( - "net/http" - "time" - - "github.com/getlantern/flashlight/v7/proxied" -) - -var ( - httpClient = getHTTPClient(proxied.ParallelForIdempotent()) -) - -// GetHTTPClient creates a new http.Client that uses domain fronting and direct -// proxies. -func GetHTTPClient() *http.Client { - return httpClient -} - -func getHTTPClient(rt http.RoundTripper) *http.Client { - return &http.Client{ - Transport: rt, - // Don't follow redirects - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return http.ErrUseLastResponse - }, - Timeout: 30 * time.Second, - } -} diff --git a/pro/http_test.go b/pro/http_test.go deleted file mode 100644 index 114a2040e..000000000 --- a/pro/http_test.go +++ /dev/null @@ -1,42 +0,0 @@ -package pro - -import ( - "io/ioutil" - "net/http" - "testing" - - "github.com/getlantern/golog" - "github.com/stretchr/testify/assert" -) - -func TestClient(t *testing.T) { - log := golog.LoggerFor("pro-http-test") - url := "https://api.getiantem.org/plans" - req, err := http.NewRequest("GET", url, nil) - if err != nil { - assert.Fail(t, "Could not get request") - } - - // Just use the default transport since otherwise test setup is difficult. - // This means it does not actually touch the proxying code, but that should - // be tested separately. - client := getHTTPClient(http.DefaultTransport) - res, e := client.Do(req) - - if !assert.NoError(t, e) { - return - } - log.Debugf("Got responsde code: %v", res.StatusCode) - assert.NotNil(t, res.Body, "nil plans response body?") - - body, bodyErr := ioutil.ReadAll(res.Body) - assert.Nil(t, bodyErr) - assert.True(t, len(body) > 0, "Should have received some body") - - res, e = client.Do(req) - assert.Nil(t, e) - - body, bodyErr = ioutil.ReadAll(res.Body) - assert.Nil(t, bodyErr) - assert.True(t, len(body) > 0, "Should have received some body") -} diff --git a/pro/migrate.go b/pro/migrate.go deleted file mode 100644 index 2777ffca0..000000000 --- a/pro/migrate.go +++ /dev/null @@ -1,18 +0,0 @@ -package pro - -import ( - "net/http" - - "github.com/getlantern/flashlight/v7/common" - "github.com/getlantern/flashlight/v7/pro/client" -) - -// MigrateDeviceID migrates the user's device ID from the old to the new scheme (only relevant to desktop builds) -func MigrateDeviceID(uc common.UserConfig, oldDeviceID string) error { - return migrateDeviceIDWithClient(uc, oldDeviceID, httpClient) -} - -func migrateDeviceIDWithClient(uc common.UserConfig, oldDeviceID string, hc *http.Client) error { - logger.Debugf("Migrating deviceID from %v to %v", oldDeviceID, uc.GetDeviceID()) - return client.NewClient(hc, PrepareProRequestWithOptions).MigrateDeviceID(uc, oldDeviceID) -} diff --git a/pro/proxy.go b/pro/proxy.go deleted file mode 100644 index 17f337696..000000000 --- a/pro/proxy.go +++ /dev/null @@ -1,156 +0,0 @@ -package pro - -import ( - "bytes" - "compress/gzip" - "encoding/json" - "io" - "io/ioutil" - "net/http" - "net/http/httputil" - "strconv" - "strings" - - "github.com/getlantern/flashlight/v7/common" - "github.com/getlantern/flashlight/v7/pro/client" - "github.com/getlantern/golog" -) - -var ( - log = golog.LoggerFor("flashlight.pro") -) - -type proxyTransport struct { - // Satisfies http.RoundTripper -} - -func (pt *proxyTransport) processOptions(req *http.Request) *http.Response { - resp := &http.Response{ - StatusCode: http.StatusOK, - Header: http.Header{ - "Connection": {"keep-alive"}, - "Via": {"Lantern Client"}, - }, - Body: ioutil.NopCloser(strings.NewReader("preflight complete")), - } - if !common.ProcessCORS(resp.Header, req) { - return &http.Response{ - StatusCode: http.StatusForbidden, - } - } - return resp -} - -func (pt *proxyTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) { - if req.Method == "OPTIONS" { - // No need to proxy the OPTIONS request. - return pt.processOptions(req), nil - } - origin := req.Header.Get("Origin") - // Workaround for https://github.com/getlantern/pro-server/issues/192 - req.Header.Del("Origin") - resp, err = GetHTTPClient().Do(req) - if err != nil { - log.Errorf("Could not issue HTTP request? %v", err) - return - } - - // Put the header back for subsequent CORS processing. - req.Header.Set("Origin", origin) - common.ProcessCORS(resp.Header, req) - if req.URL.Path != "/user-data" || resp.StatusCode != http.StatusOK { - return - } - // Try to update user data implicitly - _userID := req.Header.Get("X-Lantern-User-Id") - if _userID == "" { - log.Error("Request has an empty user ID") - return - } - userID, parseErr := strconv.ParseInt(_userID, 10, 64) - if parseErr != nil { - log.Errorf("User ID %s is invalid", _userID) - return - } - body, readErr := ioutil.ReadAll(resp.Body) - if readErr != nil { - log.Errorf("Error read response body: %v", readErr) - return - } - resp.Body = ioutil.NopCloser(bytes.NewReader(body)) - encoding := resp.Header.Get("Content-Encoding") - var br io.Reader = bytes.NewReader(body) - switch encoding { - case "gzip": - gzr, readErr := gzip.NewReader(bytes.NewReader(body)) - if readErr != nil { - log.Errorf("Unable to decode gzipped data: %v", readErr) - return - } - br = gzr - case "": - default: - log.Errorf("Unsupported response encoding %s", encoding) - return - } - user := client.User{} - readErr = json.NewDecoder(br).Decode(&user) - if readErr != nil { - log.Errorf("Error decoding JSON: %v", readErr) - return - } - log.Debugf("Updating user data implicitly for user %v", userID) - setUserData(userID, &user) - return -} - -// PrepareProRequestWithOptions normalizes requests to the pro server with -// device ID, user ID, etc set. -func PrepareProRequestWithOptions(r *http.Request, uc common.UserConfig) { - prepareProRequest(r, uc, true) -} - -// PrepareProRequest normalizes requests to the pro server without overwriting -// device ID, user ID, etc. -func PrepareProRequest(r *http.Request, uc common.UserConfig) { - prepareProRequest(r, uc, false) -} - -func prepareProRequest(r *http.Request, uc common.UserConfig, options bool) { - r.URL.Scheme = "http" - r.URL.Host = common.ProAPIHost - // XXX <03-02-22, soltzen> Requests coming from lantern-desktop's UI client - // will always carry lantern-desktop's server address (i.e., - // [here](https://github.com/getlantern/lantern-desktop/blob/87370cca9c895d0e0296b4d16e292ad8adbdae33/server/defaults_static.go#L1)) - // in their 'Host' header (like this: 'Host: localhost:16823'). This is - // problamatic for many servers (Replica's as well). So, best to either - // wipe it or assign it as the URL's host - r.Host = r.URL.Host - r.RequestURI = "" // http: Request.RequestURI can't be set in client requests. - r.Header.Set("Access-Control-Allow-Headers", strings.Join([]string{ - common.DeviceIdHeader, - common.ProTokenHeader, - common.UserIdHeader, - }, ", ")) - - // Add auth headers only if not present, to avoid race conditions - // when creating new user or switching user, i.e., linking device - // to a new account. (ovewriteAuth=false) - common.AddCommonHeadersWithOptions(uc, r, options) -} - -// APIHandler returns an HTTP handler that specifically looks for and properly -// handles pro server requests. -func APIHandler(uc common.UserConfig) http.Handler { - log.Debugf("Returning pro API handler hitting host: %v", common.ProAPIHost) - return &httputil.ReverseProxy{ - Transport: &proxyTransport{}, - Director: func(r *http.Request) { - // Strip /pro from path. - if strings.HasPrefix(r.URL.Path, "/pro/") { - r.URL.Path = r.URL.Path[4:] - } - PrepareProRequest(r, uc) - }, - } -} diff --git a/pro/proxy_test.go b/pro/proxy_test.go deleted file mode 100644 index 0b628624d..000000000 --- a/pro/proxy_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package pro - -import ( - "bytes" - "compress/gzip" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net" - "net/http" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/getlantern/flashlight/v7/common" - "github.com/getlantern/flashlight/v7/pro/client" - "github.com/getlantern/flashlight/v7/testutils" -) - -func TestProxy(t *testing.T) { - uc := common.NewUserConfigData(common.DefaultAppName, "device", 0, "token", nil, "en-US") - m := &testutils.MockRoundTripper{Header: http.Header{}, Body: strings.NewReader("GOOD")} - httpClient = &http.Client{Transport: m} - l, err := net.Listen("tcp", "localhost:0") - if !assert.NoError(t, err) { - return - } - - addr := l.Addr() - url := fmt.Sprintf("http://%s/pro/abc", addr) - t.Logf("Test server listening at %s", url) - go http.Serve(l, APIHandler(uc)) - - req, err := http.NewRequest("OPTIONS", url, nil) - if !assert.NoError(t, err) { - return - } - - origin := "http://localhost:48933" - req.Header.Set("Origin", origin) - resp, err := (&http.Client{}).Do(req) - if assert.NoError(t, err, "OPTIONS request should succeed") { - assert.Equal(t, 200, resp.StatusCode, "should respond 200 to OPTIONS") - assert.Equal(t, origin, resp.Header.Get("Access-Control-Allow-Origin"), "should respond with correct header") - _ = resp.Body.Close() - } - assert.Nil(t, m.Req, "should not pass the OPTIONS request to origin server") - - req, err = http.NewRequest("GET", url, nil) - if !assert.NoError(t, err) { - return - } - req.Header.Set("Origin", origin) - resp, err = (&http.Client{}).Do(req) - if assert.NoError(t, err, "GET request should have no error") { - assert.Equal(t, 200, resp.StatusCode, "should respond 200 ok") - assert.Equal(t, origin, resp.Header.Get("Access-Control-Allow-Origin"), "should respond with correct header") - assert.NotEmpty(t, resp.Header.Get("Access-Control-Allow-Methods"), "should respond with correct header") - msg, _ := ioutil.ReadAll(resp.Body) - _ = resp.Body.Close() - assert.Equal(t, "GOOD", string(msg), "should respond expected body") - } - if assert.NotNil(t, m.Req, "should pass through non-OPTIONS requests to origin server") { - t.Log(m.Req) - assert.Equal(t, origin, resp.Header.Get("Access-Control-Allow-Origin"), "should respond with correct header") - assert.NotEmpty(t, resp.Header.Get("Access-Control-Allow-Methods"), "should respond with correct header") - } - - url = fmt.Sprintf("http://%s/pro/user-data", addr) - msg, _ := json.Marshal(&client.User{Email: "a@a.com"}) - m.Body = bytes.NewReader(msg) - req, err = http.NewRequest("GET", url, nil) - if !assert.NoError(t, err) { - return - } - req.Header.Set("X-Lantern-User-Id", "1234") - resp, err = (&http.Client{}).Do(req) - if assert.NoError(t, err, "GET request should have no error") { - assert.Equal(t, 200, resp.StatusCode, "should respond 200 ok") - } - user, found := GetUserDataFast(1234) - if assert.True(t, found) { - assert.Equal(t, "a@a.com", user.Email, "should store user data implicitly if response is plain JSON") - } - - var gzipped bytes.Buffer - gw := gzip.NewWriter(&gzipped) - msg, _ = json.Marshal(&client.User{Email: "b@b.com"}) - io.Copy(gw, bytes.NewReader(msg)) - gw.Close() - m.Body = &gzipped - m.Header.Set("Content-Encoding", "gzip") - resp, err = (&http.Client{}).Do(req) - if assert.NoError(t, err, "GET request should have no error") { - assert.Equal(t, 200, resp.StatusCode, "should respond 200 ok") - } - user, found = GetUserDataFast(1234) - if assert.True(t, found) { - assert.Equal(t, "b@b.com", user.Email, "should store user data implicitly if response is gzipped JSON") - } -} diff --git a/pro/user_data.go b/pro/user_data.go deleted file mode 100644 index 3d907f4e0..000000000 --- a/pro/user_data.go +++ /dev/null @@ -1,162 +0,0 @@ -package pro - -import ( - "net/http" - "sync" - - "github.com/getlantern/eventual" - "github.com/getlantern/flashlight/v7/common" - "github.com/getlantern/flashlight/v7/pro/client" - "github.com/getlantern/golog" -) - -var logger = golog.LoggerFor("flashlight.app.pro") - -type userMap struct { - sync.RWMutex - data map[int64]eventual.Value - onUserData []func(current *client.User, new *client.User) -} - -var userData = userMap{ - data: make(map[int64]eventual.Value), - onUserData: make([]func(current *client.User, new *client.User), 0), -} - -// OnUserData allows registering an event handler to learn when the -// user data has been fetched. -func OnUserData(cb func(current *client.User, new *client.User)) { - userData.Lock() - userData.onUserData = append(userData.onUserData, cb) - userData.Unlock() -} - -// OnProStatusChange allows registering an event handler to learn when the -// user's pro status or "yinbi enabled" status has changed. -func OnProStatusChange(cb func(isPro bool, yinbiEnabled bool)) { - OnUserData(func(current *client.User, new *client.User) { - if current == nil || - isActive(current.UserStatus) != isActive(new.UserStatus) || - current.YinbiEnabled != new.YinbiEnabled { - cb(isActive(new.UserStatus), new.YinbiEnabled) - } - }) -} - -func (m *userMap) save(userID int64, u *client.User) { - m.Lock() - v := m.data[userID] - var current *client.User - if v == nil { - v = eventual.NewValue() - } else { - cur, _ := v.Get(0) - current, _ = cur.(*client.User) - } - v.Set(u) - m.data[userID] = v - onUserData := m.onUserData - m.Unlock() - for _, cb := range onUserData { - cb(current, u) - } -} - -func (m *userMap) get(userID int64) (*client.User, bool) { - m.RLock() - v := m.data[userID] - m.RUnlock() - if v == nil { - return nil, false - } - u, valid := v.Get(0) - if !valid { - return nil, false - } - return u.(*client.User), true -} - -// IsProUser indicates whether or not the user is pro, calling the Pro API if -// necessary to determine the status. -func IsProUser(uc common.UserConfig) (isPro bool, statusKnown bool) { - user, found := GetUserDataFast(uc.GetUserID()) - if !found { - var err error - user, err = fetchUserDataWithClient(uc, httpClient) - if err != nil { - logger.Debugf("Got error fetching pro user: %v", err) - return false, false - } - } - return isActive(user.UserStatus), true -} - -// IsProUserFast indicates whether or not the user is pro and whether or not the -// user's status is know, never calling the Pro API to determine the status. -func IsProUserFast(uc common.UserConfig) (isPro bool, statusKnown bool) { - user, found := GetUserDataFast(uc.GetUserID()) - if !found { - return false, false - } - return isActive(user.UserStatus), found -} - -// isActive determines whether the given status is an active status -func isActive(status string) bool { - return status == "active" -} - -// GetUserDataFast gets the user data for the given userID if found. -func GetUserDataFast(userID int64) (*client.User, bool) { - return userData.get(userID) -} - -// NewUser creates a new user via Pro API, and updates local cache. -func NewUser(uc common.UserConfig) (*client.User, error) { - return newUserWithClient(uc, httpClient) -} - -// NewClient creates a new pro Client -func NewClient() *client.Client { - return client.NewClient(httpClient, PrepareProRequestWithOptions) -} - -// newUserWithClient creates a new user via Pro API, and updates local cache -// using the specified http client. -func newUserWithClient(uc common.UserConfig, hc *http.Client) (*client.User, error) { - deviceID := uc.GetDeviceID() - logger.Debugf("Creating new user with device ID '%v'", deviceID) - - // use deviceID, ignore userID, token - user := common.NewUserConfigData(uc.GetAppName(), deviceID, 0, "", uc.GetInternalHeaders(), uc.GetLanguage()) - resp, err := client.NewClient(hc, PrepareProRequestWithOptions).UserCreate(user) - if err != nil { - return nil, err - } - setUserData(resp.User.Auth.ID, &resp.User) - logger.Debugf("created user %+v", resp.User) - return &resp.User, nil -} - -// FetchUserData fetches user data from Pro API, and updates local cache. -func FetchUserData(uc common.UserConfig) (*client.User, error) { - return fetchUserDataWithClient(uc, httpClient) -} - -func fetchUserDataWithClient(uc common.UserConfig, hc *http.Client) (*client.User, error) { - userID := uc.GetUserID() - logger.Debugf("Fetching user status with device ID '%v', user ID '%v' and proToken %v", uc.GetDeviceID(), userID, uc.GetToken()) - - resp, err := client.NewClient(hc, PrepareProRequestWithOptions).UserData(uc) - if err != nil { - return nil, err - } - setUserData(userID, &resp.User) - logger.Debugf("User %d is '%v'", userID, resp.User.UserStatus) - return &resp.User, nil -} - -func setUserData(userID int64, user *client.User) { - logger.Debugf("Storing user data for user %v", userID) - userData.save(userID, user) -} diff --git a/pro/user_data_test.go b/pro/user_data_test.go deleted file mode 100644 index f4f24c62c..000000000 --- a/pro/user_data_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package pro - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/getlantern/flashlight/v7/common" - "github.com/getlantern/flashlight/v7/pro/client" -) - -func TestUsers(t *testing.T) { - common.ForceStaging() - - deviceID := "77777777" - u, err := newUserWithClient(common.NewUserConfigData(common.DefaultAppName, deviceID, 0, "", nil, "en-US"), nil) - - assert.NoError(t, err, "Unexpected error") - assert.NotNil(t, u, "Should have gotten a user") - t.Logf("user: %+v", u) - - uc := common.NewUserConfigData(common.DefaultAppName, deviceID, u.Auth.ID, u.Auth.Token, nil, "en-US") - u, err = fetchUserDataWithClient(uc, nil) - assert.NoError(t, err, "Unexpected error") - assert.NotNil(t, u, "Should have gotten a user") - - delete(userData.data, u.ID) - - u, err = fetchUserDataWithClient(uc, nil) - assert.NoError(t, err, "Unexpected error") - assert.NotNil(t, u, "Should have gotten a user") - - pro, _ := IsProUser(uc) - assert.False(t, pro) - pro, _ = IsProUserFast(uc) - assert.False(t, pro) - - var waitUser int64 = 88888 - var changed int - var userDataSaved int - OnUserData(func(*client.User, *client.User) { - userDataSaved += 1 - }) - - OnProStatusChange(func(bool, bool) { - changed += 1 - }) - - userData.save(waitUser, u) - assert.Equal(t, 1, userDataSaved, "OnUserData should be called") - assert.Equal(t, 1, changed, "OnProStatusChange should be called") - - userData.save(waitUser, u) - assert.Equal(t, 2, userDataSaved, "OnUserData should be called after each saving") - assert.Equal(t, 1, changed, "OnProStatusChange should not be called again if nothing changes") - -}