relay.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. /*
  2. * Copyright (c) 2025, 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 dsl
  20. import (
  21. "bytes"
  22. "context"
  23. "crypto/tls"
  24. "crypto/x509"
  25. "encoding/json"
  26. "fmt"
  27. "io"
  28. "net/http"
  29. "os"
  30. "sync"
  31. "time"
  32. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  33. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  34. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
  35. lrucache "github.com/cognusion/go-cache-lru"
  36. "github.com/fxamacker/cbor/v2"
  37. )
  38. const (
  39. defaultMaxHttpConns = 100
  40. defaultMaxHttpIdleConns = 10
  41. defaultHttpIdleConnTimeout = 120 * time.Second
  42. defaultRequestTimeout = 30 * time.Second
  43. defaultRequestRetryCount = 1
  44. defaultServerEntryCacheTTL = 24 * time.Hour
  45. defaultServerEntryCacheMaxSize = 200000
  46. )
  47. // RelayConfig specifies the configuration for a Relay.
  48. //
  49. // The CACertificates and HostCertificate/Key parameters are used for mutually
  50. // authenticated TLS between the Relay and the DSL backend. The HostID value
  51. // is sent to the DSL backend for logging, and should be populated with the
  52. // HostID in psiphond.config.
  53. type RelayConfig struct {
  54. Logger common.Logger
  55. CACertificatesFilename string
  56. HostCertificateFilename string
  57. HostKeyFilename string
  58. GetServiceAddress func(
  59. clientGeoIPData common.GeoIPData) (string, error)
  60. HostID string
  61. // APIParameterValidator is a callback that validates base API metrics.
  62. APIParameterValidator common.APIParameterValidator
  63. // APIParameterValidator is a callback that formats base API metrics.
  64. APIParameterLogFieldFormatter common.APIParameterLogFieldFormatter
  65. }
  66. // Relay is an intermediary between a DSL client and the DSL backend which
  67. // provides circumvention and blocking resistance. Relays include in-proxy
  68. // brokers, and Psiphon servers. See the "Relay API layer" comment section is
  69. // in api.go for more details.
  70. //
  71. // The Relay maintains a pool of persistent HTTP connections for making
  72. // requests.
  73. //
  74. // The Relay supports transparent caching of server entries, where
  75. // GetServerEntriesRequest requests may be fully or partially served out of
  76. // the local cache.
  77. type Relay struct {
  78. config *RelayConfig
  79. caCertificatesFile common.ReloadableFile
  80. hostCertificateFile common.ReloadableFile
  81. hostKeyFile common.ReloadableFile
  82. mutex sync.Mutex
  83. tlsConfig *tls.Config
  84. httpClient *http.Client
  85. requestTimeout time.Duration
  86. requestRetryCount int
  87. serverEntryCache *lrucache.Cache
  88. serverEntryCacheTTL time.Duration
  89. serverEntryCacheMaxSize int
  90. }
  91. // NewRelay creates a new Relay.
  92. func NewRelay(config *RelayConfig) (*Relay, error) {
  93. relay := &Relay{
  94. config: config,
  95. caCertificatesFile: common.NewReloadableFile(config.CACertificatesFilename, false, nil),
  96. hostCertificateFile: common.NewReloadableFile(config.HostCertificateFilename, false, nil),
  97. hostKeyFile: common.NewReloadableFile(config.HostKeyFilename, false, nil),
  98. }
  99. _, err := relay.Reload()
  100. if err != nil {
  101. return nil, errors.Trace(err)
  102. }
  103. relay.SetRequestParameters(
  104. defaultMaxHttpConns,
  105. defaultMaxHttpIdleConns,
  106. defaultHttpIdleConnTimeout,
  107. defaultRequestTimeout,
  108. defaultRequestRetryCount)
  109. relay.SetCacheParameters(
  110. defaultServerEntryCacheTTL,
  111. defaultServerEntryCacheMaxSize)
  112. return relay, nil
  113. }
  114. // Reload reloads the TLS configuration when the file contents have changed.
  115. //
  116. // Reload implements the common.Reloader interface.
  117. func (r *Relay) Reload() (bool, error) {
  118. // The common.ReloadableFile.reloadAction callback not used; instead,
  119. // ReloadableFiles are used to check for changed file contents. When any
  120. // file has changed, all TLS configuration files are reloaded and the TLS
  121. // configuration is reinitialized.
  122. reloadedAny := false
  123. reloaded, err := r.caCertificatesFile.Reload()
  124. if err != nil {
  125. return false, errors.Trace(err)
  126. }
  127. reloadedAny = reloadedAny || reloaded
  128. reloaded, err = r.hostCertificateFile.Reload()
  129. if err != nil {
  130. return false, errors.Trace(err)
  131. }
  132. reloadedAny = reloadedAny || reloaded
  133. reloaded, err = r.hostKeyFile.Reload()
  134. if err != nil {
  135. return false, errors.Trace(err)
  136. }
  137. reloadedAny = reloadedAny || reloaded
  138. if !reloadedAny {
  139. return false, nil
  140. }
  141. caCertsPEM, err := os.ReadFile(r.config.CACertificatesFilename)
  142. if err != nil {
  143. return false, errors.Trace(err)
  144. }
  145. caCertificates := x509.NewCertPool()
  146. if !caCertificates.AppendCertsFromPEM(caCertsPEM) {
  147. return false, errors.TraceNew("AppendCertsFromPEM failed")
  148. }
  149. hostCertificate, err := tls.LoadX509KeyPair(
  150. r.config.HostCertificateFilename,
  151. r.config.HostKeyFilename)
  152. if err != nil {
  153. return false, errors.Trace(err)
  154. }
  155. r.mutex.Lock()
  156. defer r.mutex.Unlock()
  157. r.tlsConfig = &tls.Config{
  158. RootCAs: caCertificates,
  159. Certificates: []tls.Certificate{hostCertificate},
  160. }
  161. if r.httpClient != nil {
  162. // Replace the http.Client if it exists. See the comment in
  163. // SetRequestParameters regarding in-flight requests and idle timeout
  164. // limitations.
  165. httpTransport := r.httpClient.Transport.(*http.Transport)
  166. r.httpClient = &http.Client{
  167. Transport: &http.Transport{
  168. TLSClientConfig: r.tlsConfig,
  169. MaxConnsPerHost: httpTransport.MaxConnsPerHost,
  170. MaxIdleConns: httpTransport.MaxIdleConns,
  171. MaxIdleConnsPerHost: httpTransport.MaxIdleConnsPerHost,
  172. IdleConnTimeout: httpTransport.IdleConnTimeout,
  173. },
  174. }
  175. }
  176. return true, nil
  177. }
  178. // WillReload implements the common.Reloader interface.
  179. func (r *Relay) WillReload() bool {
  180. return true
  181. }
  182. // ReloadLogDescription implements the common.Reloader interface.
  183. func (r *Relay) ReloadLogDescription() string {
  184. return "DSL Relay TLS configuration"
  185. }
  186. // SetRequestParameters updates the HTTP request parameters used for upstream
  187. // requests.
  188. func (r *Relay) SetRequestParameters(
  189. maxHttpConns int,
  190. maxHttpIdleConns int,
  191. httpIdleConnTimeout time.Duration,
  192. requestTimeout time.Duration,
  193. requestRetryCount int) {
  194. r.mutex.Lock()
  195. defer r.mutex.Unlock()
  196. r.requestTimeout = requestTimeout
  197. r.requestRetryCount = requestRetryCount
  198. // The http.Client client is replaced when the net/http configuration has
  199. // changed. Any in-flight requests using the previous http.Client will
  200. // continue until complete and eventually the previous http.Client will
  201. // be garbage collected.
  202. //
  203. // TODO: don't retain the previous http.Client for as long as
  204. // http.Transport.IdleConnTimeout.
  205. var httpTransport *http.Transport
  206. if r.httpClient != nil {
  207. httpTransport = r.httpClient.Transport.(*http.Transport)
  208. }
  209. if r.httpClient == nil ||
  210. httpTransport.MaxConnsPerHost != maxHttpConns ||
  211. httpTransport.MaxIdleConns != maxHttpIdleConns ||
  212. httpTransport.IdleConnTimeout != httpIdleConnTimeout {
  213. r.httpClient = &http.Client{
  214. Transport: &http.Transport{
  215. TLSClientConfig: r.tlsConfig,
  216. MaxConnsPerHost: maxHttpConns,
  217. MaxIdleConns: maxHttpIdleConns,
  218. MaxIdleConnsPerHost: maxHttpIdleConns,
  219. IdleConnTimeout: httpIdleConnTimeout,
  220. },
  221. }
  222. }
  223. }
  224. // SetCacheParameters updates the parameters used for transparent server
  225. // entry caching. When the parameters change, any existing cache is flushed
  226. // and replaced.
  227. func (r *Relay) SetCacheParameters(
  228. TTL time.Duration,
  229. maxSize int) {
  230. r.mutex.Lock()
  231. defer r.mutex.Unlock()
  232. if r.serverEntryCache == nil ||
  233. r.serverEntryCacheTTL != TTL ||
  234. r.serverEntryCacheMaxSize != maxSize {
  235. if r.serverEntryCache != nil {
  236. r.serverEntryCache.Flush()
  237. }
  238. r.serverEntryCacheTTL = TTL
  239. r.serverEntryCacheMaxSize = maxSize
  240. if r.serverEntryCacheTTL > 0 {
  241. r.serverEntryCache = lrucache.NewWithLRU(
  242. r.serverEntryCacheTTL,
  243. 1*time.Minute,
  244. r.serverEntryCacheMaxSize)
  245. } else {
  246. r.serverEntryCache = nil
  247. }
  248. }
  249. }
  250. // HandleRequest relays a DSL request.
  251. //
  252. // If an extendTimeout callback is specified, it will be called with the
  253. // expected maximum request timeout, including retries; this callback may be
  254. // used to customize the response timeout for a transport handler.
  255. //
  256. // Set isClientTunneled when the relay uses a connected Psiphon tunnel.
  257. //
  258. // In the case of an error, the caller must log the error and send
  259. // dsl.GenericErrorResponse to the client. This generic error response
  260. // ensures that the client receives a DSL response and doesn't consider the
  261. // DSL FetcherRoundTripper to have failed.
  262. func (r *Relay) HandleRequest(
  263. ctx context.Context,
  264. extendTimeout func(time.Duration),
  265. clientIP string,
  266. clientGeoIPData common.GeoIPData,
  267. isClientTunneled bool,
  268. cborRelayedRequest []byte) ([]byte, error) {
  269. r.mutex.Lock()
  270. httpClient := r.httpClient
  271. requestTimeout := r.requestTimeout
  272. requestRetryCount := r.requestRetryCount
  273. r.mutex.Unlock()
  274. if extendTimeout != nil {
  275. extendTimeout(requestTimeout * time.Duration(requestRetryCount))
  276. }
  277. if httpClient == nil {
  278. return nil, errors.TraceNew("missing http client")
  279. }
  280. if len(cborRelayedRequest) > MaxRelayPayloadSize {
  281. return nil, errors.Tracef(
  282. "request size %d exceeds limit %d",
  283. len(cborRelayedRequest), MaxRelayPayloadSize)
  284. }
  285. var relayedRequest *RelayedRequest
  286. err := cbor.Unmarshal(cborRelayedRequest, &relayedRequest)
  287. if err != nil {
  288. return nil, errors.Trace(err)
  289. }
  290. if relayedRequest.Version != requestVersion {
  291. return nil, errors.Tracef(
  292. "unexpected request version %d", relayedRequest.Version)
  293. }
  294. path, ok := requestTypeToHTTPPath[relayedRequest.RequestType]
  295. if !ok {
  296. return nil, errors.Tracef(
  297. "unknown request type %d", relayedRequest.RequestType)
  298. }
  299. // Transparent caching:
  300. //
  301. // For requestTypeGetServerEntries, peek at the RelayedResponse.Response
  302. // and extract server entries and add to the local cache, keyed by server
  303. // entry tag.
  304. //
  305. // Peek at RelayedRequest.Request, and if all requested server entries are
  306. // in the cache, serve the request entirely from the local cache.
  307. //
  308. // The backend DSL may enforce a limited time interval in which certain
  309. // server entries can be discovered. This cache doesn't bypass this,
  310. // since DiscoveryServerEntries isn't cached and always passed through to
  311. // the DSL backend. Clients must discover the large, random server entry
  312. // tags via DiscoveryServerEntries within the designated time interval;
  313. // then clients may download the server entries via GetServerEntries at
  314. // any time, and this may be cached.
  315. //
  316. // Limitation: this cache ignores server entry version and may serve a
  317. // version that's older that the latest within the cache TTL.
  318. //
  319. // - Server entry version changes are assumed to be rare.
  320. //
  321. // - The cache will be updated with a new version as soon as
  322. // cacheGetServerEntriesResponse sees it.
  323. //
  324. // - Use a reasonable TTL such as 24h; cache entry TTLs aren't extended on
  325. // hits, so any old version will eventually be removed.
  326. //
  327. // - A more complicated scheme is possible: also peek at
  328. // DiscoverServerEntriesResponses and, for each tag/version pair, if
  329. // the tag is in the cache and the cached entry is an old version,
  330. // delete from the cache. This would require unpacking each server entry.
  331. var response []byte
  332. cachedResponse := false
  333. if relayedRequest.RequestType == requestTypeGetServerEntries {
  334. var err error
  335. response, err = r.getCachedGetServerEntriesResponse(
  336. relayedRequest.Request, clientGeoIPData)
  337. if err != nil {
  338. r.config.Logger.WithTraceFields(common.LogFields{
  339. "error": err.Error(),
  340. }).Warning("DSL: serve cached response failed")
  341. // Proceed with relaying request, even if the failure was due to
  342. // an error in DecodePackedAPIParameters or APIParameterValidator.
  343. // This allows the DSL backend to make the authoritative decision
  344. // and also log all failure cases.
  345. }
  346. cachedResponse = err == nil && response != nil
  347. }
  348. for i := 0; !cachedResponse; i++ {
  349. requestCtx := ctx
  350. if requestTimeout > 0 {
  351. var requestCancelFunc context.CancelFunc
  352. requestCtx, requestCancelFunc = context.WithTimeout(ctx, requestTimeout)
  353. defer requestCancelFunc()
  354. }
  355. serviceAddress, err := r.config.GetServiceAddress(clientGeoIPData)
  356. if err != nil {
  357. return nil, errors.Trace(err)
  358. }
  359. url := fmt.Sprintf("https://%s%s", serviceAddress, path)
  360. httpRequest, err := http.NewRequestWithContext(
  361. requestCtx, "POST", url, bytes.NewBuffer(relayedRequest.Request))
  362. if err != nil {
  363. return nil, errors.Trace(err)
  364. }
  365. // Attach the client IP and GeoIPData. The raw IP may be used, by the
  366. // DSL backend, in server entry selection logic; the GeoIP data is
  367. // for stats, and may also be used in server entry selection logic.
  368. // Sending preresolved GeoIP data saves the DSL backend from needing
  369. // its own GeoIP resolver, and ensures, for a given client a
  370. // consistent GeoIP view between the Psiphon server and the DSL backend.
  371. jsonGeoIPData, err := json.Marshal(clientGeoIPData)
  372. if err != nil {
  373. return nil, errors.Trace(err)
  374. }
  375. httpRequest.Header.Set(PsiphonClientIPHeader, clientIP)
  376. httpRequest.Header.Set(PsiphonClientGeoIPDataHeader, string(jsonGeoIPData))
  377. if isClientTunneled {
  378. httpRequest.Header.Set(PsiphonClientTunneledHeader, "true")
  379. } else {
  380. httpRequest.Header.Set(PsiphonClientTunneledHeader, "false")
  381. }
  382. httpRequest.Header.Set(PsiphonHostIDHeader, r.config.HostID)
  383. startTime := time.Now()
  384. httpResponse, err := httpClient.Do(httpRequest)
  385. duration := time.Since(startTime)
  386. if err == nil && httpResponse.StatusCode != http.StatusOK {
  387. httpResponse.Body.Close()
  388. err = errors.Tracef("unexpected response code: %d", httpResponse.StatusCode)
  389. }
  390. if err == nil {
  391. response, err = io.ReadAll(httpResponse.Body)
  392. httpResponse.Body.Close()
  393. }
  394. if err == nil {
  395. if relayedRequest.RequestType == requestTypeGetServerEntries {
  396. err := r.cacheGetServerEntriesResponse(
  397. relayedRequest.Request, response)
  398. if err != nil {
  399. r.config.Logger.WithTraceFields(common.LogFields{
  400. "error": err.Error(),
  401. }).Warning("DSL: cache response failed")
  402. // Proceed with relaying response
  403. }
  404. }
  405. break
  406. }
  407. r.config.Logger.WithTraceFields(common.LogFields{
  408. "duration": duration.String(),
  409. "error": err.Error(),
  410. }).Warning("DSL: service request attempt failed")
  411. // Retry on network errors.
  412. if i < requestRetryCount && ctx.Err() == nil {
  413. continue
  414. }
  415. return nil, errors.Tracef("all attempts failed")
  416. }
  417. // Compress GetServerEntriesResponse responses.
  418. //
  419. // The CBOR-encoded SourcedServerEntry/protocol.PackedServerEntryFields
  420. // items in GetServerEntriesResponse benefit from compression due to
  421. // repeating server entry values. Only this response is compressed, as
  422. // other responses almost completely consist of non-repeating random
  423. // values.
  424. //
  425. // Compression is only added at the relay->client hop, to avoid additonal
  426. // CPU load on the DSL backend, and avoid relays having to always
  427. // decompress the backend response in cacheGetServerEntriesResponse.
  428. compression := common.CompressionNone
  429. if relayedRequest.RequestType == requestTypeGetServerEntries {
  430. compression = common.CompressionZlib
  431. }
  432. compressedResponse, err := common.Compress(compression, response)
  433. if err != nil {
  434. return nil, errors.Trace(err)
  435. }
  436. cborRelayedResponse, err := protocol.CBOREncoding.Marshal(
  437. &RelayedResponse{
  438. Compression: compression,
  439. Response: compressedResponse,
  440. })
  441. if err != nil {
  442. return nil, errors.Trace(err)
  443. }
  444. if len(cborRelayedResponse) > MaxRelayPayloadSize {
  445. return nil, errors.Tracef(
  446. "response size %d exceeds limit %d",
  447. len(cborRelayedResponse), MaxRelayPayloadSize)
  448. }
  449. return cborRelayedResponse, nil
  450. }
  451. func (r *Relay) cacheGetServerEntriesResponse(
  452. cborRequest []byte,
  453. cborResponse []byte) error {
  454. if r.serverEntryCacheTTL == 0 {
  455. // Caching is disabled
  456. return nil
  457. }
  458. var request GetServerEntriesRequest
  459. err := cbor.Unmarshal(cborRequest, &request)
  460. if err != nil {
  461. return errors.Trace(err)
  462. }
  463. var response GetServerEntriesResponse
  464. err = cbor.Unmarshal(cborResponse, &response)
  465. if err != nil {
  466. return errors.Trace(err)
  467. }
  468. if len(request.ServerEntryTags) != len(response.SourcedServerEntries) {
  469. return errors.TraceNew("unexpected entry count mismatch")
  470. }
  471. for i, serverEntryTag := range request.ServerEntryTags {
  472. if response.SourcedServerEntries[i] != nil {
  473. // This will update any existing cached copy of the server entry for
  474. // this tag, in case the server entry version is new. This also
  475. // extends the cache TTL, since the server entry is fresh.
  476. r.serverEntryCache.Set(
  477. string(serverEntryTag),
  478. response.SourcedServerEntries[i],
  479. lrucache.DefaultExpiration)
  480. } else {
  481. // In this case, the DSL backend is indicating that the server
  482. // entry for the requested tag no longer exists, perhaps due to
  483. // server pruning since the DiscoverServerEntries request. This
  484. // is an edge case since DiscoverServerEntries won't return
  485. // invalid tags and so the "nil" value/state isn't cached.
  486. r.serverEntryCache.Delete(string(serverEntryTag))
  487. }
  488. }
  489. return nil
  490. }
  491. func (r *Relay) getCachedGetServerEntriesResponse(
  492. cborRequest []byte,
  493. clientGeoIPData common.GeoIPData) ([]byte, error) {
  494. if r.serverEntryCacheTTL == 0 {
  495. // Caching is disabled
  496. return nil, nil
  497. }
  498. var request GetServerEntriesRequest
  499. err := cbor.Unmarshal(cborRequest, &request)
  500. if err != nil {
  501. return nil, errors.Trace(err)
  502. }
  503. // Since we anticipate that most server entries will be cached, allocate
  504. // response slices optimistically.
  505. //
  506. // TODO: check for sufficient cache entries before allocating these
  507. // response slices? Would doubling the cache lookups use less resources
  508. // than unused allocations?
  509. serverEntryTags := make([]string, len(request.ServerEntryTags))
  510. var response GetServerEntriesResponse
  511. response.SourcedServerEntries = make([]*SourcedServerEntry, len(request.ServerEntryTags))
  512. for i, serverEntryTag := range request.ServerEntryTags {
  513. cacheEntry, ok := r.serverEntryCache.Get(string(serverEntryTag))
  514. if !ok {
  515. // The request can't be served from the cache, as some server
  516. // entry tags aren't present. Fall back to a full request to the
  517. // DSL backend.
  518. //
  519. // As a potential future enhancement, consider partially serving
  520. // from the cache, after making a DSL request for just the
  521. // unknown server entries?
  522. return nil, nil
  523. }
  524. // The cached entry's TTL is not extended on a hit.
  525. // serverEntryTags are used for logging the request event when served
  526. // from the cache.
  527. serverEntryTags[i] = serverEntryTag.String()
  528. response.SourcedServerEntries[i] = cacheEntry.(*SourcedServerEntry)
  529. }
  530. cborResponse, err := protocol.CBOREncoding.Marshal(&response)
  531. if err != nil {
  532. return nil, errors.Trace(err)
  533. }
  534. // Log the request event. Since this request is server from the relay
  535. // cache, the DSL backend will not see the request and log the event
  536. // itself. This log should match the DSL log format and can be shipped to
  537. // the same log aggregator.
  538. baseParams, err := protocol.DecodePackedAPIParameters(request.BaseAPIParameters)
  539. if err != nil {
  540. return nil, errors.Trace(err)
  541. }
  542. err = r.config.APIParameterValidator(baseParams)
  543. if err != nil {
  544. return nil, errors.Trace(err)
  545. }
  546. logFields := r.config.APIParameterLogFieldFormatter("", clientGeoIPData, baseParams)
  547. logFields["server_entry_tags"] = serverEntryTags
  548. r.config.Logger.LogMetric("dsl_relay_get_server_entries", logFields)
  549. return cborResponse, nil
  550. }
  551. var relayGenericErrorResponse []byte
  552. func init() {
  553. // Pre-marshal a generic, non-revealing error code to return on any
  554. // upstream failure.
  555. cborErrorResponse, err := protocol.CBOREncoding.Marshal(
  556. &RelayedResponse{
  557. Error: 1,
  558. })
  559. if err != nil {
  560. panic(err.Error())
  561. }
  562. relayGenericErrorResponse = cborErrorResponse
  563. }
  564. func GetRelayGenericErrorResponse() []byte {
  565. return relayGenericErrorResponse
  566. }