frontingDialParameters.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. package psiphon
  2. import (
  3. "context"
  4. "net"
  5. "net/http"
  6. "strconv"
  7. "sync/atomic"
  8. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
  9. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  10. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/fragmentor"
  11. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
  12. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
  13. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
  14. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/resolver"
  15. utls "github.com/Psiphon-Labs/utls"
  16. "golang.org/x/net/bpf"
  17. )
  18. // FrontedMeekDialParameters represents a selected fronting transport and all
  19. // the related protocol attributes, many chosen at random, for a fronted dial
  20. // attempt.
  21. //
  22. // FrontedMeekDialParameters is used:
  23. // - to configure dialers
  24. // - as a persistent record to store successful dial parameters for replay
  25. // - to report dial stats in notices and Psiphon API calls.
  26. //
  27. // FrontedMeekDialParameters is similar to tunnel DialParameters, but is
  28. // specific to fronted meek. It should be used for all fronted meek dials,
  29. // apart from the tunnel DialParameters cases.
  30. //
  31. // prepareDialConfigs must be called on any unmarshaled
  32. // FrontedMeekDialParameters. For example, when unmarshaled from a replay
  33. // record.
  34. //
  35. // resolvedIPAddress is set asynchronously, as it is not known until the dial
  36. // process has begun. The atomic.Value will contain a string, initialized to
  37. // "", and set to the resolved IP address once that part of the dial process
  38. // has completed.
  39. //
  40. // FrontedMeekDialParameters is not safe for concurrent use.
  41. type FrontedMeekDialParameters struct {
  42. NetworkLatencyMultiplier float64
  43. FrontingTransport string
  44. DialAddress string
  45. FrontingProviderID string
  46. FrontingDialAddress string
  47. SNIServerName string
  48. TransformedHostName bool
  49. VerifyServerName string
  50. VerifyPins []string
  51. HostHeader string
  52. resolvedIPAddress atomic.Value `json:"-"`
  53. TLSProfile string
  54. TLSVersion string
  55. RandomizedTLSProfileSeed *prng.Seed
  56. NoDefaultTLSSessionID bool
  57. TLSFragmentClientHello bool
  58. SelectedUserAgent bool
  59. UserAgent string
  60. BPFProgramName string
  61. BPFProgramInstructions []bpf.RawInstruction
  62. FragmentorSeed *prng.Seed
  63. ResolveParameters *resolver.ResolveParameters
  64. dialConfig *DialConfig `json:"-"`
  65. meekConfig *MeekConfig `json:"-"`
  66. }
  67. // makeFrontedMeekDialParameters creates a new FrontedMeekDialParameters for
  68. // configuring a fronted HTTP client, including selecting a fronting transport,
  69. // and all the various protocol attributes.
  70. //
  71. // payloadSecure must only be set if all HTTP plaintext payloads sent through
  72. // the returned net/http.Client will be wrapped in their own transport security
  73. // layer, which permits skipping of server certificate verification.
  74. func makeFrontedMeekDialParameters(
  75. config *Config,
  76. p parameters.ParametersAccessor,
  77. tunnel *Tunnel,
  78. frontingSpecs parameters.FrontingSpecs,
  79. selectedFrontingProviderID func(string),
  80. useDeviceBinder,
  81. skipVerify,
  82. disableSystemRootCAs,
  83. payloadSecure bool) (*FrontedMeekDialParameters, error) {
  84. // This function duplicates some code from MakeDialParameters. To simplify
  85. // the logic, the Replay<Component> tactic flags for individual dial
  86. // components are ignored.
  87. //
  88. // TODO: merge common functionality?
  89. if !payloadSecure && (skipVerify || disableSystemRootCAs) {
  90. return nil, errors.TraceNew("cannot skip certificate verification if payload insecure")
  91. }
  92. frontedMeekDialParams := FrontedMeekDialParameters{}
  93. // Network latency multiplier
  94. frontedMeekDialParams.NetworkLatencyMultiplier = prng.ExpFloat64Range(
  95. p.Float(parameters.NetworkLatencyMultiplierMin),
  96. p.Float(parameters.NetworkLatencyMultiplierMax),
  97. p.Float(parameters.NetworkLatencyMultiplierLambda))
  98. // Select fronting configuration
  99. var err error
  100. frontedMeekDialParams.FrontingProviderID,
  101. frontedMeekDialParams.FrontingTransport,
  102. frontedMeekDialParams.FrontingDialAddress,
  103. frontedMeekDialParams.SNIServerName,
  104. frontedMeekDialParams.VerifyServerName,
  105. frontedMeekDialParams.VerifyPins,
  106. frontedMeekDialParams.HostHeader,
  107. err = frontingSpecs.SelectParameters()
  108. if err != nil {
  109. return nil, errors.Trace(err)
  110. }
  111. // At this time, the transport is limited to fronted HTTPS.
  112. //
  113. // As a future enhancement, allow HTTP in certain cases (e.g. the in-proxy
  114. // broker case), skip selecting TLS tactics and select HTTP tactics such as
  115. // HTTPTransformerParameters.
  116. if frontedMeekDialParams.FrontingTransport == protocol.FRONTING_TRANSPORT_HTTP {
  117. return nil, errors.TraceNew("unsupported fronting transport")
  118. }
  119. if selectedFrontingProviderID != nil {
  120. selectedFrontingProviderID(frontedMeekDialParams.FrontingProviderID)
  121. }
  122. // FrontingSpec.Addresses may include a port; default to 443 if none.
  123. if _, _, err := net.SplitHostPort(frontedMeekDialParams.FrontingDialAddress); err == nil {
  124. frontedMeekDialParams.DialAddress = frontedMeekDialParams.FrontingDialAddress
  125. } else {
  126. frontedMeekDialParams.DialAddress = net.JoinHostPort(frontedMeekDialParams.FrontingDialAddress, "443")
  127. }
  128. // Determine and use the equivalent tunnel protocol for tactics
  129. // selections. For example, for the broker transport FRONTED-HTTPS, use
  130. // the tactics for FRONTED-MEEK-OSSH.
  131. equivalentTunnelProtocol, err := protocol.EquivilentTunnelProtocol(frontedMeekDialParams.FrontingTransport)
  132. if err != nil {
  133. return nil, errors.Trace(err)
  134. }
  135. // SNI configuration
  136. //
  137. // For a FrontingSpec, an SNI value of "" indicates to disable/omit SNI, so
  138. // never transform in that case.
  139. if frontedMeekDialParams.SNIServerName != "" {
  140. if p.WeightedCoinFlip(parameters.TransformHostNameProbability) {
  141. frontedMeekDialParams.SNIServerName = selectHostName(equivalentTunnelProtocol, p)
  142. frontedMeekDialParams.TransformedHostName = true
  143. }
  144. }
  145. // TLS configuration
  146. //
  147. // In the in-proxy case, the requireTLS13 flag is set to true, and
  148. // requireTLS12SessionTickets to false, in order to use only modern TLS
  149. // fingerprints which should support HTTP/2 in the ALPN.
  150. //
  151. // TODO: TLS padding
  152. requireTLS12SessionTickets :=
  153. !protocol.TunnelProtocolUsesInproxy(equivalentTunnelProtocol) &&
  154. protocol.TunnelProtocolRequiresTLS12SessionTickets(
  155. equivalentTunnelProtocol)
  156. requireTLS13Support :=
  157. protocol.TunnelProtocolUsesInproxy(equivalentTunnelProtocol) ||
  158. protocol.TunnelProtocolRequiresTLS13Support(equivalentTunnelProtocol)
  159. isFronted := true
  160. frontedMeekDialParams.TLSProfile,
  161. frontedMeekDialParams.TLSVersion,
  162. frontedMeekDialParams.RandomizedTLSProfileSeed,
  163. err = SelectTLSProfile(requireTLS12SessionTickets, requireTLS13Support, isFronted, frontedMeekDialParams.FrontingProviderID, p)
  164. if err != nil {
  165. return nil, errors.Trace(err)
  166. }
  167. if frontedMeekDialParams.TLSProfile == "" && (requireTLS12SessionTickets || requireTLS13Support) {
  168. return nil, errors.TraceNew("required TLS profile not found")
  169. }
  170. frontedMeekDialParams.NoDefaultTLSSessionID = p.WeightedCoinFlip(
  171. parameters.NoDefaultTLSSessionIDProbability)
  172. if frontedMeekDialParams.SNIServerName != "" && net.ParseIP(frontedMeekDialParams.SNIServerName) == nil {
  173. tlsFragmentorLimitProtocols := p.TunnelProtocols(parameters.TLSFragmentClientHelloLimitProtocols)
  174. if len(tlsFragmentorLimitProtocols) == 0 || common.Contains(tlsFragmentorLimitProtocols, equivalentTunnelProtocol) {
  175. frontedMeekDialParams.TLSFragmentClientHello = p.WeightedCoinFlip(parameters.TLSFragmentClientHelloProbability)
  176. }
  177. }
  178. // User Agent configuration
  179. dialCustomHeaders := makeDialCustomHeaders(config, p)
  180. frontedMeekDialParams.SelectedUserAgent, frontedMeekDialParams.UserAgent = selectUserAgentIfUnset(p, dialCustomHeaders)
  181. // Resolver configuration
  182. //
  183. // The custom resolver is wired up only when there is a domain to be
  184. // resolved; GetMetrics will log resolver metrics when the resolver is set.
  185. if net.ParseIP(frontedMeekDialParams.DialAddress) == nil {
  186. resolver := config.GetResolver()
  187. if resolver == nil {
  188. return nil, errors.TraceNew("missing resolver")
  189. }
  190. frontedMeekDialParams.ResolveParameters, err = resolver.MakeResolveParameters(
  191. p, frontedMeekDialParams.FrontingProviderID, frontedMeekDialParams.DialAddress)
  192. if err != nil {
  193. return nil, errors.Trace(err)
  194. }
  195. }
  196. if tunnel == nil {
  197. // BPF configuration
  198. if ClientBPFEnabled() &&
  199. protocol.TunnelProtocolMayUseClientBPF(equivalentTunnelProtocol) {
  200. if p.WeightedCoinFlip(parameters.BPFClientTCPProbability) {
  201. frontedMeekDialParams.BPFProgramName = ""
  202. frontedMeekDialParams.BPFProgramInstructions = nil
  203. ok, name, rawInstructions := p.BPFProgram(parameters.BPFClientTCPProgram)
  204. if ok {
  205. frontedMeekDialParams.BPFProgramName = name
  206. frontedMeekDialParams.BPFProgramInstructions = rawInstructions
  207. }
  208. }
  209. }
  210. // Fragmentor configuration
  211. frontedMeekDialParams.FragmentorSeed, err = prng.NewSeed()
  212. if err != nil {
  213. return nil, errors.Trace(err)
  214. }
  215. }
  216. // Initialize Dial/MeekConfigs to be passed to the corresponding dialers.
  217. err = frontedMeekDialParams.prepareDialConfigs(
  218. config, p, tunnel, dialCustomHeaders, useDeviceBinder, skipVerify,
  219. disableSystemRootCAs, payloadSecure)
  220. if err != nil {
  221. return nil, errors.Trace(err)
  222. }
  223. return &frontedMeekDialParams, nil
  224. }
  225. // prepareDialConfigs is called for both new and replayed dial parameters.
  226. func (f *FrontedMeekDialParameters) prepareDialConfigs(
  227. config *Config,
  228. p parameters.ParametersAccessor,
  229. tunnel *Tunnel,
  230. dialCustomHeaders http.Header,
  231. useDeviceBinder,
  232. skipVerify,
  233. disableSystemRootCAs,
  234. payloadSecure bool) error {
  235. if !payloadSecure && (skipVerify || disableSystemRootCAs) {
  236. return errors.TraceNew("cannot skip certificate verification if payload insecure")
  237. }
  238. equivilentTunnelProtocol, err := protocol.EquivilentTunnelProtocol(f.FrontingTransport)
  239. if err != nil {
  240. return errors.Trace(err)
  241. }
  242. // Custom headers and User Agent
  243. if dialCustomHeaders == nil {
  244. dialCustomHeaders = makeDialCustomHeaders(config, p)
  245. }
  246. if f.SelectedUserAgent {
  247. dialCustomHeaders.Set("User-Agent", f.UserAgent)
  248. }
  249. // Fragmentor
  250. fragmentorConfig := fragmentor.NewUpstreamConfig(
  251. p, equivilentTunnelProtocol, f.FragmentorSeed)
  252. // Resolver
  253. //
  254. // DialConfig.ResolveIP is required and called even when the destination
  255. // is an IP address.
  256. resolver := config.GetResolver()
  257. if resolver == nil {
  258. return errors.TraceNew("missing resolver")
  259. }
  260. // DialConfig
  261. f.resolvedIPAddress.Store("")
  262. var resolveIP func(context.Context, string) ([]net.IP, error)
  263. if tunnel != nil {
  264. tunneledDialer := func(_, addr string) (net.Conn, error) {
  265. // Set alwaysTunneled to ensure the http.Client traffic is always tunneled,
  266. // even when split tunnel mode is enabled.
  267. conn, _, err := tunnel.DialTCPChannel(addr, true, nil)
  268. return conn, errors.Trace(err)
  269. }
  270. f.dialConfig = &DialConfig{
  271. DiagnosticID: f.FrontingProviderID,
  272. TrustedCACertificatesFilename: config.TrustedCACertificatesFilename,
  273. CustomDialer: func(_ context.Context, _, addr string) (net.Conn, error) {
  274. return tunneledDialer("", addr)
  275. },
  276. }
  277. } else {
  278. resolveIP = func(ctx context.Context, hostname string) ([]net.IP, error) {
  279. IPs, err := UntunneledResolveIP(
  280. ctx, config, resolver, hostname, f.FrontingProviderID)
  281. if err != nil {
  282. return nil, errors.Trace(err)
  283. }
  284. return IPs, nil
  285. }
  286. var deviceBinder DeviceBinder
  287. if useDeviceBinder {
  288. deviceBinder = config.DeviceBinder
  289. }
  290. f.dialConfig = &DialConfig{
  291. DiagnosticID: f.FrontingProviderID,
  292. UpstreamProxyURL: config.UpstreamProxyURL,
  293. CustomHeaders: dialCustomHeaders,
  294. BPFProgramInstructions: f.BPFProgramInstructions,
  295. TrustedCACertificatesFilename: config.TrustedCACertificatesFilename,
  296. FragmentorConfig: fragmentorConfig,
  297. DeviceBinder: deviceBinder,
  298. IPv6Synthesizer: config.IPv6Synthesizer,
  299. ResolveIP: resolveIP,
  300. ResolvedIPCallback: func(IPAddress string) {
  301. f.resolvedIPAddress.Store(IPAddress)
  302. },
  303. }
  304. }
  305. // MeekDialConfig
  306. // Note: if MeekModeRelay or MeekModeObfuscatedRoundTrip are supported in the
  307. // future, set MeekObfuscatorPaddingSeed.
  308. var meekMode MeekMode = MeekModePlaintextRoundTrip
  309. if payloadSecure {
  310. meekMode = MeekModeWrappedPlaintextRoundTrip
  311. }
  312. addPsiphonFrontingHeader := false
  313. if f.FrontingProviderID != "" {
  314. addPsiphonFrontingHeader = common.Contains(
  315. p.LabeledTunnelProtocols(
  316. parameters.AddFrontingProviderPsiphonFrontingHeader,
  317. f.FrontingProviderID),
  318. equivilentTunnelProtocol)
  319. }
  320. f.meekConfig = &MeekConfig{
  321. DiagnosticID: f.FrontingProviderID,
  322. Parameters: config.GetParameters(),
  323. Mode: meekMode,
  324. DialAddress: f.DialAddress,
  325. TLSProfile: f.TLSProfile,
  326. TLSFragmentClientHello: f.TLSFragmentClientHello,
  327. NoDefaultTLSSessionID: f.NoDefaultTLSSessionID,
  328. RandomizedTLSProfileSeed: f.RandomizedTLSProfileSeed,
  329. SNIServerName: f.SNIServerName,
  330. HostHeader: f.HostHeader,
  331. TransformedHostName: f.TransformedHostName,
  332. AddPsiphonFrontingHeader: addPsiphonFrontingHeader,
  333. VerifyServerName: f.VerifyServerName,
  334. VerifyPins: f.VerifyPins,
  335. ClientTunnelProtocol: equivilentTunnelProtocol,
  336. NetworkLatencyMultiplier: f.NetworkLatencyMultiplier,
  337. AdditionalHeaders: config.MeekAdditionalHeaders,
  338. // TODO: Change hard-coded session key be something like FrontingProviderID + BrokerID.
  339. // This is necessary once longer-term TLS caches are added.
  340. // The meek dial address, based on the fronting dial address returned by
  341. // parameters.FrontingSpecs.SelectParameters has couple of issues. For some providers there's
  342. // only a couple or even just one possible value, in other cases there are millions of possible values
  343. // and cached values won't be used as often as they ought to be.
  344. TLSClientSessionCache: common.WrapUtlsClientSessionCache(utls.NewLRUClientSessionCache(0), f.DialAddress),
  345. }
  346. if !skipVerify {
  347. f.meekConfig.DisableSystemRootCAs = disableSystemRootCAs
  348. if !f.meekConfig.DisableSystemRootCAs {
  349. f.meekConfig.VerifyServerName = f.VerifyServerName
  350. f.meekConfig.VerifyPins = f.VerifyPins
  351. }
  352. }
  353. switch f.FrontingTransport {
  354. case protocol.FRONTING_TRANSPORT_HTTPS:
  355. f.meekConfig.UseHTTPS = true
  356. case protocol.FRONTING_TRANSPORT_QUIC:
  357. // TODO: configure QUIC tactics
  358. f.meekConfig.UseQUIC = true
  359. }
  360. return nil
  361. }
  362. // GetMetrics returns log fields detailing the fronted meek dial parameters.
  363. // All log field names are prefixed with overridePrefix, when specified, which
  364. // also overrides any default prefixes.
  365. func (meekDialParameters *FrontedMeekDialParameters) GetMetrics(overridePrefix string) common.LogFields {
  366. prefix := ""
  367. meekPrefix := "meek_"
  368. if overridePrefix != "" {
  369. prefix = overridePrefix
  370. meekPrefix = overridePrefix
  371. }
  372. logFields := make(common.LogFields)
  373. logFields[prefix+"fronting_provider_id"] = meekDialParameters.FrontingProviderID
  374. if meekDialParameters.DialAddress != "" {
  375. logFields[meekPrefix+"dial_address"] = meekDialParameters.DialAddress
  376. }
  377. meekResolvedIPAddress := meekDialParameters.resolvedIPAddress.Load().(string)
  378. if meekResolvedIPAddress != "" {
  379. logFields[meekPrefix+"resolved_ip_address"] = meekResolvedIPAddress
  380. }
  381. if meekDialParameters.SNIServerName != "" {
  382. logFields[meekPrefix+"sni_server_name"] = meekDialParameters.SNIServerName
  383. }
  384. if meekDialParameters.HostHeader != "" {
  385. logFields[meekPrefix+"host_header"] = meekDialParameters.HostHeader
  386. }
  387. transformedHostName := "0"
  388. if meekDialParameters.TransformedHostName {
  389. transformedHostName = "1"
  390. }
  391. logFields[meekPrefix+"transformed_host_name"] = transformedHostName
  392. if meekDialParameters.SelectedUserAgent {
  393. logFields[prefix+"user_agent"] = meekDialParameters.UserAgent
  394. }
  395. if meekDialParameters.FrontingTransport == protocol.FRONTING_TRANSPORT_HTTPS {
  396. if meekDialParameters.TLSProfile != "" {
  397. logFields[prefix+"tls_profile"] = meekDialParameters.TLSProfile
  398. }
  399. if meekDialParameters.TLSVersion != "" {
  400. logFields[prefix+"tls_version"] =
  401. getTLSVersionForMetrics(meekDialParameters.TLSVersion, meekDialParameters.NoDefaultTLSSessionID)
  402. }
  403. tlsFragmented := "0"
  404. if meekDialParameters.TLSFragmentClientHello {
  405. tlsFragmented = "1"
  406. }
  407. logFields[prefix+"tls_fragmented"] = tlsFragmented
  408. }
  409. if meekDialParameters.BPFProgramName != "" {
  410. logFields[prefix+"client_bpf"] = meekDialParameters.BPFProgramName
  411. }
  412. if meekDialParameters.ResolveParameters != nil {
  413. // See comment for dialParams.ResolveParameters handling in
  414. // getBaseAPIParameters.
  415. if meekDialParameters.ResolveParameters.PreresolvedIPAddress != "" {
  416. dialDomain, _, _ := net.SplitHostPort(meekDialParameters.meekConfig.DialAddress)
  417. if meekDialParameters.ResolveParameters.PreresolvedDomain == dialDomain {
  418. logFields[prefix+"dns_preresolved"] = meekDialParameters.ResolveParameters.PreresolvedIPAddress
  419. }
  420. }
  421. if meekDialParameters.ResolveParameters.PreferAlternateDNSServer {
  422. logFields[prefix+"dns_preferred"] = meekDialParameters.ResolveParameters.AlternateDNSServer
  423. }
  424. if meekDialParameters.ResolveParameters.ProtocolTransformName != "" {
  425. logFields[prefix+"dns_transform"] = meekDialParameters.ResolveParameters.ProtocolTransformName
  426. }
  427. logFields[prefix+"dns_attempt"] = strconv.Itoa(
  428. meekDialParameters.ResolveParameters.GetFirstAttemptWithAnswer())
  429. }
  430. // TODO: get fragmentor metrics, if any, from MeekConn.
  431. return logFields
  432. }