frontingDialParameters.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  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,
  84. tlsCache utls.ClientSessionCache) (*FrontedMeekDialParameters, error) {
  85. // This function duplicates some code from MakeDialParameters. To simplify
  86. // the logic, the Replay<Component> tactic flags for individual dial
  87. // components are ignored.
  88. //
  89. // TODO: merge common functionality?
  90. if !payloadSecure && (skipVerify || disableSystemRootCAs) {
  91. return nil, errors.TraceNew("cannot skip certificate verification if payload insecure")
  92. }
  93. frontedMeekDialParams := FrontedMeekDialParameters{}
  94. // Network latency multiplier
  95. frontedMeekDialParams.NetworkLatencyMultiplier = prng.ExpFloat64Range(
  96. p.Float(parameters.NetworkLatencyMultiplierMin),
  97. p.Float(parameters.NetworkLatencyMultiplierMax),
  98. p.Float(parameters.NetworkLatencyMultiplierLambda))
  99. // Select fronting configuration
  100. var err error
  101. frontedMeekDialParams.FrontingProviderID,
  102. frontedMeekDialParams.FrontingTransport,
  103. frontedMeekDialParams.FrontingDialAddress,
  104. frontedMeekDialParams.SNIServerName,
  105. frontedMeekDialParams.VerifyServerName,
  106. frontedMeekDialParams.VerifyPins,
  107. frontedMeekDialParams.HostHeader,
  108. err = frontingSpecs.SelectParameters()
  109. if err != nil {
  110. return nil, errors.Trace(err)
  111. }
  112. // At this time, the transport is limited to fronted HTTPS.
  113. //
  114. // As a future enhancement, allow HTTP in certain cases (e.g. the in-proxy
  115. // broker case), skip selecting TLS tactics and select HTTP tactics such as
  116. // HTTPTransformerParameters; and allow QUIC and select QUIC tactics.
  117. if frontedMeekDialParams.FrontingTransport != protocol.FRONTING_TRANSPORT_HTTPS {
  118. return nil, errors.TraceNew("unsupported fronting transport")
  119. }
  120. if selectedFrontingProviderID != nil {
  121. selectedFrontingProviderID(frontedMeekDialParams.FrontingProviderID)
  122. }
  123. // FrontingSpec.Addresses may include a port; default to 443 if none.
  124. if _, _, err := net.SplitHostPort(frontedMeekDialParams.FrontingDialAddress); err == nil {
  125. frontedMeekDialParams.DialAddress = frontedMeekDialParams.FrontingDialAddress
  126. } else {
  127. frontedMeekDialParams.DialAddress = net.JoinHostPort(frontedMeekDialParams.FrontingDialAddress, "443")
  128. }
  129. // Determine and use the equivalent tunnel protocol for tactics
  130. // selections. For example, for the broker transport FRONTED-HTTPS, use
  131. // the tactics for FRONTED-MEEK-OSSH.
  132. equivalentTunnelProtocol, err := protocol.EquivilentTunnelProtocol(frontedMeekDialParams.FrontingTransport)
  133. if err != nil {
  134. return nil, errors.Trace(err)
  135. }
  136. // SNI configuration
  137. //
  138. // For a FrontingSpec, an SNI value of "" indicates to disable/omit SNI, so
  139. // never transform in that case.
  140. if frontedMeekDialParams.SNIServerName != "" {
  141. if p.WeightedCoinFlip(parameters.TransformHostNameProbability) {
  142. frontedMeekDialParams.SNIServerName = selectHostName(equivalentTunnelProtocol, p)
  143. frontedMeekDialParams.TransformedHostName = true
  144. }
  145. }
  146. // TLS configuration
  147. //
  148. // In the in-proxy case, the requireTLS13 flag is set to true, and
  149. // requireTLS12SessionTickets to false, in order to use only modern TLS
  150. // fingerprints which should support HTTP/2 in the ALPN.
  151. //
  152. // TODO: TLS padding
  153. requireTLS12SessionTickets :=
  154. !protocol.TunnelProtocolUsesInproxy(equivalentTunnelProtocol) &&
  155. protocol.TunnelProtocolRequiresTLS12SessionTickets(
  156. equivalentTunnelProtocol)
  157. requireTLS13Support :=
  158. protocol.TunnelProtocolUsesInproxy(equivalentTunnelProtocol) ||
  159. protocol.TunnelProtocolRequiresTLS13Support(equivalentTunnelProtocol)
  160. isFronted := true
  161. frontedMeekDialParams.TLSProfile,
  162. frontedMeekDialParams.TLSVersion,
  163. frontedMeekDialParams.RandomizedTLSProfileSeed,
  164. err = SelectTLSProfile(requireTLS12SessionTickets, requireTLS13Support, isFronted, frontedMeekDialParams.FrontingProviderID, p)
  165. if err != nil {
  166. return nil, errors.Trace(err)
  167. }
  168. if frontedMeekDialParams.TLSProfile == "" && (requireTLS12SessionTickets || requireTLS13Support) {
  169. return nil, errors.TraceNew("required TLS profile not found")
  170. }
  171. frontedMeekDialParams.NoDefaultTLSSessionID = p.WeightedCoinFlip(
  172. parameters.NoDefaultTLSSessionIDProbability)
  173. if frontedMeekDialParams.SNIServerName != "" && net.ParseIP(frontedMeekDialParams.SNIServerName) == nil {
  174. tlsFragmentorLimitProtocols := p.TunnelProtocols(parameters.TLSFragmentClientHelloLimitProtocols)
  175. if len(tlsFragmentorLimitProtocols) == 0 || common.Contains(tlsFragmentorLimitProtocols, equivalentTunnelProtocol) {
  176. frontedMeekDialParams.TLSFragmentClientHello = p.WeightedCoinFlip(parameters.TLSFragmentClientHelloProbability)
  177. }
  178. }
  179. // User Agent configuration
  180. dialCustomHeaders := makeDialCustomHeaders(config, p)
  181. frontedMeekDialParams.SelectedUserAgent, frontedMeekDialParams.UserAgent = selectUserAgentIfUnset(p, dialCustomHeaders)
  182. // Resolver configuration
  183. //
  184. // The custom resolver is wired up only when there is a domain to be
  185. // resolved; GetMetrics will log resolver metrics when the resolver is set.
  186. if net.ParseIP(frontedMeekDialParams.DialAddress) == nil {
  187. resolver := config.GetResolver()
  188. if resolver == nil {
  189. return nil, errors.TraceNew("missing resolver")
  190. }
  191. frontedMeekDialParams.ResolveParameters, err = resolver.MakeResolveParameters(
  192. p, frontedMeekDialParams.FrontingProviderID, frontedMeekDialParams.DialAddress)
  193. if err != nil {
  194. return nil, errors.Trace(err)
  195. }
  196. }
  197. if tunnel == nil {
  198. // BPF configuration
  199. if ClientBPFEnabled() &&
  200. protocol.TunnelProtocolMayUseClientBPF(equivalentTunnelProtocol) {
  201. if p.WeightedCoinFlip(parameters.BPFClientTCPProbability) {
  202. frontedMeekDialParams.BPFProgramName = ""
  203. frontedMeekDialParams.BPFProgramInstructions = nil
  204. ok, name, rawInstructions := p.BPFProgram(parameters.BPFClientTCPProgram)
  205. if ok {
  206. frontedMeekDialParams.BPFProgramName = name
  207. frontedMeekDialParams.BPFProgramInstructions = rawInstructions
  208. }
  209. }
  210. }
  211. // Fragmentor configuration
  212. frontedMeekDialParams.FragmentorSeed, err = prng.NewSeed()
  213. if err != nil {
  214. return nil, errors.Trace(err)
  215. }
  216. }
  217. // Initialize Dial/MeekConfigs to be passed to the corresponding dialers.
  218. err = frontedMeekDialParams.prepareDialConfigs(
  219. config, p, tunnel, dialCustomHeaders, useDeviceBinder, skipVerify,
  220. disableSystemRootCAs, payloadSecure, tlsCache)
  221. if err != nil {
  222. return nil, errors.Trace(err)
  223. }
  224. return &frontedMeekDialParams, nil
  225. }
  226. // prepareDialConfigs is called for both new and replayed dial parameters.
  227. func (f *FrontedMeekDialParameters) prepareDialConfigs(
  228. config *Config,
  229. p parameters.ParametersAccessor,
  230. tunnel *Tunnel,
  231. dialCustomHeaders http.Header,
  232. useDeviceBinder,
  233. skipVerify,
  234. disableSystemRootCAs,
  235. payloadSecure bool,
  236. tlsCache utls.ClientSessionCache) error {
  237. if !payloadSecure && (skipVerify || disableSystemRootCAs) {
  238. return errors.TraceNew("cannot skip certificate verification if payload insecure")
  239. }
  240. equivilentTunnelProtocol, err := protocol.EquivilentTunnelProtocol(f.FrontingTransport)
  241. if err != nil {
  242. return errors.Trace(err)
  243. }
  244. // Custom headers and User Agent
  245. if dialCustomHeaders == nil {
  246. dialCustomHeaders = makeDialCustomHeaders(config, p)
  247. }
  248. if f.SelectedUserAgent {
  249. dialCustomHeaders.Set("User-Agent", f.UserAgent)
  250. }
  251. // Fragmentor
  252. fragmentorConfig := fragmentor.NewUpstreamConfig(
  253. p, equivilentTunnelProtocol, f.FragmentorSeed)
  254. // Resolver
  255. //
  256. // DialConfig.ResolveIP is required and called even when the destination
  257. // is an IP address.
  258. resolver := config.GetResolver()
  259. if resolver == nil {
  260. return errors.TraceNew("missing resolver")
  261. }
  262. // DialConfig
  263. f.resolvedIPAddress.Store("")
  264. var resolveIP func(context.Context, string) ([]net.IP, error)
  265. if tunnel != nil {
  266. tunneledDialer := func(_, addr string) (net.Conn, error) {
  267. // Set alwaysTunneled to ensure the http.Client traffic is always tunneled,
  268. // even when split tunnel mode is enabled.
  269. conn, _, err := tunnel.DialTCPChannel(addr, true, nil)
  270. return conn, errors.Trace(err)
  271. }
  272. f.dialConfig = &DialConfig{
  273. DiagnosticID: f.FrontingProviderID,
  274. TrustedCACertificatesFilename: config.TrustedCACertificatesFilename,
  275. CustomDialer: func(_ context.Context, _, addr string) (net.Conn, error) {
  276. return tunneledDialer("", addr)
  277. },
  278. }
  279. } else {
  280. resolveIP = func(ctx context.Context, hostname string) ([]net.IP, error) {
  281. IPs, err := UntunneledResolveIP(
  282. ctx, config, resolver, hostname, f.FrontingProviderID)
  283. if err != nil {
  284. return nil, errors.Trace(err)
  285. }
  286. return IPs, nil
  287. }
  288. var deviceBinder DeviceBinder
  289. if useDeviceBinder {
  290. deviceBinder = config.DeviceBinder
  291. }
  292. f.dialConfig = &DialConfig{
  293. DiagnosticID: f.FrontingProviderID,
  294. UpstreamProxyURL: config.UpstreamProxyURL,
  295. CustomHeaders: dialCustomHeaders,
  296. BPFProgramInstructions: f.BPFProgramInstructions,
  297. TrustedCACertificatesFilename: config.TrustedCACertificatesFilename,
  298. FragmentorConfig: fragmentorConfig,
  299. DeviceBinder: deviceBinder,
  300. IPv6Synthesizer: config.IPv6Synthesizer,
  301. ResolveIP: resolveIP,
  302. ResolvedIPCallback: func(IPAddress string) {
  303. f.resolvedIPAddress.Store(IPAddress)
  304. },
  305. }
  306. }
  307. // MeekDialConfig
  308. // Note: if MeekModeRelay or MeekModeObfuscatedRoundTrip are supported in the
  309. // future, set MeekObfuscatorPaddingSeed.
  310. var meekMode MeekMode = MeekModePlaintextRoundTrip
  311. if payloadSecure {
  312. meekMode = MeekModeWrappedPlaintextRoundTrip
  313. }
  314. addFrontingHeader := addPsiphonFrontingHeader(
  315. p,
  316. f.FrontingProviderID,
  317. equivilentTunnelProtocol,
  318. f.DialAddress,
  319. f.ResolveParameters)
  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: addFrontingHeader,
  333. VerifyServerName: f.VerifyServerName,
  334. VerifyPins: f.VerifyPins,
  335. ClientTunnelProtocol: equivilentTunnelProtocol,
  336. NetworkLatencyMultiplier: f.NetworkLatencyMultiplier,
  337. AdditionalHeaders: config.MeekAdditionalHeaders,
  338. // CustomTLSDial will use the resolved IP address as the session key.
  339. TLSClientSessionCache: common.WrapUtlsClientSessionCache(tlsCache, common.TLS_NULL_SESSION_KEY),
  340. }
  341. if !skipVerify {
  342. f.meekConfig.DisableSystemRootCAs = disableSystemRootCAs
  343. if !f.meekConfig.DisableSystemRootCAs {
  344. f.meekConfig.VerifyServerName = f.VerifyServerName
  345. f.meekConfig.VerifyPins = f.VerifyPins
  346. }
  347. }
  348. switch f.FrontingTransport {
  349. case protocol.FRONTING_TRANSPORT_HTTPS:
  350. f.meekConfig.UseHTTPS = true
  351. case protocol.FRONTING_TRANSPORT_QUIC:
  352. // TODO: configure QUIC tactics
  353. f.meekConfig.UseQUIC = true
  354. }
  355. return nil
  356. }
  357. // GetMetrics returns log fields detailing the fronted meek dial parameters.
  358. // All log field names are prefixed with overridePrefix, when specified, which
  359. // also overrides any default prefixes.
  360. func (meekDialParameters *FrontedMeekDialParameters) GetMetrics(overridePrefix string) common.LogFields {
  361. prefix := ""
  362. meekPrefix := "meek_"
  363. if overridePrefix != "" {
  364. prefix = overridePrefix
  365. meekPrefix = overridePrefix
  366. }
  367. logFields := make(common.LogFields)
  368. logFields[prefix+"fronting_provider_id"] = meekDialParameters.FrontingProviderID
  369. if meekDialParameters.DialAddress != "" {
  370. logFields[meekPrefix+"dial_address"] = meekDialParameters.DialAddress
  371. }
  372. meekResolvedIPAddress := meekDialParameters.resolvedIPAddress.Load().(string)
  373. if meekResolvedIPAddress != "" {
  374. logFields[meekPrefix+"resolved_ip_address"] = meekResolvedIPAddress
  375. }
  376. if meekDialParameters.SNIServerName != "" {
  377. logFields[meekPrefix+"sni_server_name"] = meekDialParameters.SNIServerName
  378. }
  379. if meekDialParameters.HostHeader != "" {
  380. logFields[meekPrefix+"host_header"] = meekDialParameters.HostHeader
  381. }
  382. transformedHostName := "0"
  383. if meekDialParameters.TransformedHostName {
  384. transformedHostName = "1"
  385. }
  386. logFields[meekPrefix+"transformed_host_name"] = transformedHostName
  387. if meekDialParameters.SelectedUserAgent {
  388. logFields[prefix+"user_agent"] = meekDialParameters.UserAgent
  389. }
  390. if meekDialParameters.FrontingTransport == protocol.FRONTING_TRANSPORT_HTTPS {
  391. if meekDialParameters.TLSProfile != "" {
  392. logFields[prefix+"tls_profile"] = meekDialParameters.TLSProfile
  393. }
  394. if meekDialParameters.TLSVersion != "" {
  395. logFields[prefix+"tls_version"] =
  396. getTLSVersionForMetrics(meekDialParameters.TLSVersion, meekDialParameters.NoDefaultTLSSessionID)
  397. }
  398. tlsFragmented := "0"
  399. if meekDialParameters.TLSFragmentClientHello {
  400. tlsFragmented = "1"
  401. }
  402. logFields[prefix+"tls_fragmented"] = tlsFragmented
  403. }
  404. if meekDialParameters.BPFProgramName != "" {
  405. logFields[prefix+"client_bpf"] = meekDialParameters.BPFProgramName
  406. }
  407. if meekDialParameters.ResolveParameters != nil {
  408. // See comment for dialParams.ResolveParameters handling in
  409. // getBaseAPIParameters.
  410. if meekDialParameters.ResolveParameters.PreresolvedIPAddress != "" {
  411. dialDomain, _, _ := net.SplitHostPort(meekDialParameters.meekConfig.DialAddress)
  412. if meekDialParameters.ResolveParameters.PreresolvedDomain == dialDomain {
  413. logFields[prefix+"dns_preresolved"] = meekDialParameters.ResolveParameters.PreresolvedIPAddress
  414. }
  415. }
  416. if meekDialParameters.ResolveParameters.PreferAlternateDNSServer {
  417. logFields[prefix+"dns_preferred"] = meekDialParameters.ResolveParameters.AlternateDNSServer
  418. }
  419. if meekDialParameters.ResolveParameters.ProtocolTransformName != "" {
  420. logFields[prefix+"dns_transform"] = meekDialParameters.ResolveParameters.ProtocolTransformName
  421. }
  422. if meekDialParameters.ResolveParameters.RandomQNameCasingSeed != nil {
  423. logFields[prefix+"dns_qname_random_casing"] = "1"
  424. }
  425. if meekDialParameters.ResolveParameters.ResponseQNameMustMatch {
  426. logFields[prefix+"dns_qname_must_match"] = "1"
  427. }
  428. logFields[prefix+"dns_qname_mismatches"] = strconv.Itoa(
  429. meekDialParameters.ResolveParameters.GetQNameMismatches())
  430. logFields[prefix+"dns_attempt"] = strconv.Itoa(
  431. meekDialParameters.ResolveParameters.GetFirstAttemptWithAnswer())
  432. }
  433. // TODO: get fragmentor metrics, if any, from MeekConn.
  434. return logFields
  435. }