brokerClient.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /*
  2. * Copyright (c) 2023, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package inproxy
  20. import (
  21. "context"
  22. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  23. )
  24. // BrokerClient is used to make requests to a broker.
  25. //
  26. // Each BrokerClient maintains a secure broker session. A BrokerClient and its
  27. // session may be used for multiple concurrent requests. Session key material
  28. // is provided by DialParameters and must remain static for the lifetime of
  29. // the BrokerClient.
  30. //
  31. // Round trips between the BrokerClient and broker are provided by
  32. // BrokerClientRoundTripper from DialParameters. The RoundTripper must
  33. // maintain the association between a request payload and the corresponding
  34. // response payload. The canonical RoundTripper is an HTTP client, with
  35. // HTTP/2 or HTTP/3 used to multiplex concurrent requests.
  36. //
  37. // When the DialParameters BrokerClientRoundTripperSucceeded call back is
  38. // invoked, the RoundTripper provider may mark the RoundTripper dial
  39. // properties for replay.
  40. //
  41. // When the DialParameters BrokerClientRoundTripperFailed call back is
  42. // invoked, the RoundTripper provider should clear any replay state and also
  43. // create a new RoundTripper to be returned from BrokerClientRoundTripper.
  44. //
  45. // BrokerClient does not have a Close operation. The user should close the
  46. // provided RoundTripper as appropriate.
  47. //
  48. // The secure session layer includes obfuscation that provides random padding
  49. // and uniformly random payload content. The RoundTripper is expected to add
  50. // its own obfuscation layer; for example, domain fronting.
  51. type BrokerClient struct {
  52. dialParams DialParameters
  53. sessions *InitiatorSessions
  54. }
  55. // NewBrokerClient initializes a new BrokerClient with the provided
  56. // DialParameters.
  57. func NewBrokerClient(dialParams DialParameters) (*BrokerClient, error) {
  58. // A client is expected to use an ephemeral key, and can return a
  59. // zero-value private key. Each proxy should use a peristent key, as the
  60. // corresponding public key is the proxy ID, which is used to credit the
  61. // proxy for its service.
  62. privateKey := dialParams.BrokerClientPrivateKey()
  63. if privateKey.IsZero() {
  64. var err error
  65. privateKey, err = GenerateSessionPrivateKey()
  66. if err != nil {
  67. return nil, errors.Trace(err)
  68. }
  69. }
  70. return &BrokerClient{
  71. dialParams: dialParams,
  72. sessions: NewInitiatorSessions(privateKey),
  73. }, nil
  74. }
  75. // ProxyAnnounce sends a ProxyAnnounce request and returns the response.
  76. func (b *BrokerClient) ProxyAnnounce(
  77. ctx context.Context,
  78. request *ProxyAnnounceRequest) (*ProxyAnnounceResponse, error) {
  79. requestPayload, err := MarshalProxyAnnounceRequest(request)
  80. if err != nil {
  81. return nil, errors.Trace(err)
  82. }
  83. responsePayload, err := b.roundTrip(ctx, requestPayload)
  84. if err != nil {
  85. return nil, errors.Trace(err)
  86. }
  87. response, err := UnmarshalProxyAnnounceResponse(responsePayload)
  88. if err != nil {
  89. return nil, errors.Trace(err)
  90. }
  91. return response, nil
  92. }
  93. // ClientOffer sends a ClientOffer request and returns the response.
  94. func (b *BrokerClient) ClientOffer(
  95. ctx context.Context,
  96. request *ClientOfferRequest) (*ClientOfferResponse, error) {
  97. requestPayload, err := MarshalClientOfferRequest(request)
  98. if err != nil {
  99. return nil, errors.Trace(err)
  100. }
  101. responsePayload, err := b.roundTrip(ctx, requestPayload)
  102. if err != nil {
  103. return nil, errors.Trace(err)
  104. }
  105. response, err := UnmarshalClientOfferResponse(responsePayload)
  106. if err != nil {
  107. return nil, errors.Trace(err)
  108. }
  109. return response, nil
  110. }
  111. // ProxyAnswer sends a ProxyAnswer request and returns the response.
  112. func (b *BrokerClient) ProxyAnswer(
  113. ctx context.Context,
  114. request *ProxyAnswerRequest) (*ProxyAnswerResponse, error) {
  115. requestPayload, err := MarshalProxyAnswerRequest(request)
  116. if err != nil {
  117. return nil, errors.Trace(err)
  118. }
  119. responsePayload, err := b.roundTrip(ctx, requestPayload)
  120. if err != nil {
  121. return nil, errors.Trace(err)
  122. }
  123. response, err := UnmarshalProxyAnswerResponse(responsePayload)
  124. if err != nil {
  125. return nil, errors.Trace(err)
  126. }
  127. return response, nil
  128. }
  129. // ClientRelayedPacket sends a ClientRelayedPacket request and returns the
  130. // response.
  131. func (b *BrokerClient) ClientRelayedPacket(
  132. ctx context.Context,
  133. request *ClientRelayedPacketRequest) (*ClientRelayedPacketResponse, error) {
  134. requestPayload, err := MarshalClientRelayedPacketRequest(request)
  135. if err != nil {
  136. return nil, errors.Trace(err)
  137. }
  138. responsePayload, err := b.roundTrip(ctx, requestPayload)
  139. if err != nil {
  140. return nil, errors.Trace(err)
  141. }
  142. response, err := UnmarshalClientRelayedPacketResponse(responsePayload)
  143. if err != nil {
  144. return nil, errors.Trace(err)
  145. }
  146. return response, nil
  147. }
  148. func (b *BrokerClient) roundTrip(
  149. ctx context.Context,
  150. request []byte) ([]byte, error) {
  151. // The round tripper may need to establish a transport-level connection;
  152. // or this may already be established.
  153. roundTripper, err := b.dialParams.BrokerClientRoundTripper()
  154. if err != nil {
  155. return nil, errors.Trace(err)
  156. }
  157. // InitiatorSessions.RoundTrip may make serveral round trips with
  158. // roundTripper in order to complete a session establishment handshake.
  159. //
  160. // When there's an active session, only a single round trip is required,
  161. // to exchange the application-level request and response.
  162. //
  163. // When a concurrent BrokerClient request is currently performing a
  164. // session handshake, InitiatorSessions.RoundTrip will await completion
  165. // of that handshake before sending the application-layer request.
  166. //
  167. // Retries are built in to InitiatorSessions.RoundTrip: if there's an
  168. // existing session and it's expired, there will be additional round
  169. // trips to establish a fresh session.
  170. //
  171. // While the round tripper is responsible for maintaining the
  172. // request/response association, the application-level request and
  173. // response are tagged with a RoundTripID which is checked to ensure the
  174. // association is maintained.
  175. waitToShareSession := true
  176. response, err := b.sessions.RoundTrip(
  177. ctx,
  178. roundTripper,
  179. b.dialParams.BrokerPublicKey(),
  180. b.dialParams.BrokerRootObfuscationSecret(),
  181. waitToShareSession,
  182. request)
  183. if err != nil {
  184. // The DialParameters provider should close the existing
  185. // BrokerClientRoundTripper and create a new RoundTripper to return
  186. // in the next BrokerClientRoundTripper call.
  187. //
  188. // The session will be closed, if necessary, by InitiatorSessions.
  189. // It's possible that the session remains valid and only the
  190. // RoundTripper transport layer needs to be reset.
  191. b.dialParams.BrokerClientRoundTripperFailed(roundTripper)
  192. return nil, errors.Trace(err)
  193. }
  194. b.dialParams.BrokerClientRoundTripperSucceeded(roundTripper)
  195. return response, nil
  196. }