|
|
@@ -0,0 +1,612 @@
|
|
|
+package psiphon
|
|
|
+
|
|
|
+import (
|
|
|
+ "context"
|
|
|
+ "net"
|
|
|
+ "net/http"
|
|
|
+ "strconv"
|
|
|
+ "sync/atomic"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common"
|
|
|
+ "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
|
|
|
+ "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/fragmentor"
|
|
|
+ "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/parameters"
|
|
|
+ "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/prng"
|
|
|
+ "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/protocol"
|
|
|
+ "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/resolver"
|
|
|
+ utls "github.com/Psiphon-Labs/utls"
|
|
|
+ "golang.org/x/net/bpf"
|
|
|
+)
|
|
|
+
|
|
|
+// frontedHTTPDialParameters represents a selected fronting transport and dial
|
|
|
+// parameters.
|
|
|
+//
|
|
|
+// frontedHTTPDialParameters is used to configure dialers; as a persistent
|
|
|
+// record to store successful dial parameters for replay; and to report dial
|
|
|
+// stats in notices and Psiphon API calls.
|
|
|
+//
|
|
|
+// frontedHTTPDialParameters is similar to tunnel DialParameters, but is
|
|
|
+// specific to TransferURLs.
|
|
|
+type frontedHTTPDialParameters struct {
|
|
|
+ isReplay bool `json:"-"`
|
|
|
+
|
|
|
+ LastUsedTimestamp time.Time
|
|
|
+ LastUsedFrontingSpecHash []byte
|
|
|
+
|
|
|
+ FrontedMeekDialParameters *FrontedMeekDialParameters
|
|
|
+}
|
|
|
+
|
|
|
+// makeFrontedHTTPDialParameters creates a new frontedHTTPDialParameters for
|
|
|
+// configuring a fronted HTTP client, including selecting a fronting transport
|
|
|
+// and all the various protocol attributes.
|
|
|
+//
|
|
|
+// payloadSecure must only be set if all HTTP plaintext payloads sent through
|
|
|
+// the returned net/http.Client will be wrapped in their own transport security
|
|
|
+// layer, which permits skipping of server certificate verification.
|
|
|
+func makeFrontedHTTPDialParameters(
|
|
|
+ config *Config,
|
|
|
+ p parameters.ParametersAccessor,
|
|
|
+ tunnel *Tunnel,
|
|
|
+ frontingSpec *parameters.FrontingSpec,
|
|
|
+ selectedFrontingProviderID func(string),
|
|
|
+ useDeviceBinder,
|
|
|
+ skipVerify,
|
|
|
+ disableSystemRootCAs,
|
|
|
+ payloadSecure bool) (*frontedHTTPDialParameters, error) {
|
|
|
+
|
|
|
+ currentTimestamp := time.Now()
|
|
|
+
|
|
|
+ dialParams := &frontedHTTPDialParameters{
|
|
|
+ LastUsedTimestamp: currentTimestamp,
|
|
|
+ LastUsedFrontingSpecHash: hashFrontingSpec(frontingSpec),
|
|
|
+ }
|
|
|
+
|
|
|
+ var err error
|
|
|
+ dialParams.FrontedMeekDialParameters, err = makeFrontedMeekDialParameters(
|
|
|
+ config,
|
|
|
+ p,
|
|
|
+ tunnel,
|
|
|
+ parameters.FrontingSpecs{frontingSpec},
|
|
|
+ selectedFrontingProviderID,
|
|
|
+ useDeviceBinder,
|
|
|
+ skipVerify,
|
|
|
+ disableSystemRootCAs,
|
|
|
+ payloadSecure,
|
|
|
+ )
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.Trace(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Initialize Dial/MeekConfigs to be passed to the corresponding dialers.
|
|
|
+
|
|
|
+ err = dialParams.prepareDialConfig(
|
|
|
+ config,
|
|
|
+ p,
|
|
|
+ false,
|
|
|
+ tunnel,
|
|
|
+ skipVerify,
|
|
|
+ disableSystemRootCAs,
|
|
|
+ useDeviceBinder,
|
|
|
+ payloadSecure)
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.Trace(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return dialParams, nil
|
|
|
+}
|
|
|
+
|
|
|
+// prepareDialConfig is called for both new and replayed dial parameters.
|
|
|
+func (dialParams *frontedHTTPDialParameters) prepareDialConfig(config *Config,
|
|
|
+ p parameters.ParametersAccessor,
|
|
|
+ isReplay bool,
|
|
|
+ tunnel *Tunnel,
|
|
|
+ useDeviceBinder,
|
|
|
+ skipVerify,
|
|
|
+ disableSystemRootCAs,
|
|
|
+ payloadSecure bool) error {
|
|
|
+
|
|
|
+ dialParams.isReplay = isReplay
|
|
|
+
|
|
|
+ // Initialize DialConfig to be passed to the corresponding dialers.
|
|
|
+
|
|
|
+ if isReplay {
|
|
|
+ err := dialParams.FrontedMeekDialParameters.prepareDialConfig(
|
|
|
+ config, p, tunnel, nil, useDeviceBinder, skipVerify,
|
|
|
+ disableSystemRootCAs, payloadSecure)
|
|
|
+ if err != nil {
|
|
|
+ return errors.Trace(err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// FrontedMeekDialParameters represents a selected fronting transport and dial
|
|
|
+// parameters.
|
|
|
+//
|
|
|
+// FrontedMeekDialParameters is used to configure dialers; and to report dial
|
|
|
+// stats in notices and Psiphon API calls.
|
|
|
+//
|
|
|
+// FrontedMeekDialParameters is similar to tunnel DialParameters, but is
|
|
|
+// specific to the in-proxy broker dial phase and TransferURLs.
|
|
|
+type FrontedMeekDialParameters struct {
|
|
|
+ NetworkLatencyMultiplier float64
|
|
|
+
|
|
|
+ FrontingTransport string
|
|
|
+
|
|
|
+ DialAddress string
|
|
|
+
|
|
|
+ FrontingProviderID string
|
|
|
+ FrontingDialAddress string
|
|
|
+ SNIServerName string
|
|
|
+ TransformedHostName bool
|
|
|
+ VerifyServerName string
|
|
|
+ VerifyPins []string
|
|
|
+ HostHeader string
|
|
|
+ resolvedIPAddress atomic.Value `json:"-"`
|
|
|
+
|
|
|
+ TLSProfile string
|
|
|
+ TLSVersion string
|
|
|
+ RandomizedTLSProfileSeed *prng.Seed
|
|
|
+ NoDefaultTLSSessionID bool
|
|
|
+ TLSFragmentClientHello bool
|
|
|
+
|
|
|
+ SelectedUserAgent bool
|
|
|
+ UserAgent string
|
|
|
+
|
|
|
+ BPFProgramName string
|
|
|
+ BPFProgramInstructions []bpf.RawInstruction
|
|
|
+
|
|
|
+ FragmentorSeed *prng.Seed
|
|
|
+
|
|
|
+ ResolveParameters *resolver.ResolveParameters
|
|
|
+
|
|
|
+ dialConfig *DialConfig `json:"-"`
|
|
|
+ meekConfig *MeekConfig `json:"-"`
|
|
|
+}
|
|
|
+
|
|
|
+// makeFrontedMeekDialParameters creates a new FrontedMeekDialParameters for
|
|
|
+// configuring a fronted HTTP client, including selecting a fronting transport,
|
|
|
+// and all the various protocol attributes.
|
|
|
+//
|
|
|
+// payloadSecure must only be set if all HTTP plaintext payloads sent through
|
|
|
+// the returned net/http.Client will be wrapped in their own transport security
|
|
|
+// layer, which permits skipping of server certificate verification.
|
|
|
+func makeFrontedMeekDialParameters(
|
|
|
+ config *Config,
|
|
|
+ p parameters.ParametersAccessor,
|
|
|
+ tunnel *Tunnel,
|
|
|
+ frontingSpecs parameters.FrontingSpecs,
|
|
|
+ selectedFrontingProviderID func(string),
|
|
|
+ useDeviceBinder,
|
|
|
+ skipVerify,
|
|
|
+ disableSystemRootCAs,
|
|
|
+ payloadSecure bool) (*FrontedMeekDialParameters, error) {
|
|
|
+
|
|
|
+ // This function duplicates some code from MakeDialParameters. To simplify
|
|
|
+ // the logic, the Replay<Component> tactic flags for individual dial
|
|
|
+ // components are ignored.
|
|
|
+ //
|
|
|
+ // TODO: merge common functionality?
|
|
|
+
|
|
|
+ if !payloadSecure && (skipVerify || disableSystemRootCAs) {
|
|
|
+ return nil, errors.TraceNew("cannot skip certificate verification if payload insecure")
|
|
|
+ }
|
|
|
+
|
|
|
+ frontedMeekDialParams := FrontedMeekDialParameters{}
|
|
|
+
|
|
|
+ // Network latency multiplier
|
|
|
+
|
|
|
+ frontedMeekDialParams.NetworkLatencyMultiplier = prng.ExpFloat64Range(
|
|
|
+ p.Float(parameters.NetworkLatencyMultiplierMin),
|
|
|
+ p.Float(parameters.NetworkLatencyMultiplierMax),
|
|
|
+ p.Float(parameters.NetworkLatencyMultiplierLambda))
|
|
|
+
|
|
|
+ // Select fronting configuration
|
|
|
+
|
|
|
+ var err error
|
|
|
+
|
|
|
+ frontedMeekDialParams.FrontingProviderID,
|
|
|
+ frontedMeekDialParams.FrontingTransport,
|
|
|
+ frontedMeekDialParams.FrontingDialAddress,
|
|
|
+ frontedMeekDialParams.SNIServerName,
|
|
|
+ frontedMeekDialParams.VerifyServerName,
|
|
|
+ frontedMeekDialParams.VerifyPins,
|
|
|
+ frontedMeekDialParams.HostHeader,
|
|
|
+ err = frontingSpecs.SelectParameters()
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.Trace(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // At this time, the transport is limited to fronted HTTPS.
|
|
|
+ //
|
|
|
+ // As a future enhancement, allow HTTP in certain cases (e.g. the in-proxy
|
|
|
+ // broker case), skip selecting TLS tactics and select HTTP tactics such as
|
|
|
+ // HTTPTransformerParameters.
|
|
|
+
|
|
|
+ if frontedMeekDialParams.FrontingTransport == protocol.FRONTING_TRANSPORT_HTTP {
|
|
|
+ return nil, errors.TraceNew("unsupported fronting transport")
|
|
|
+ }
|
|
|
+
|
|
|
+ if selectedFrontingProviderID != nil {
|
|
|
+ selectedFrontingProviderID(frontedMeekDialParams.FrontingProviderID)
|
|
|
+ }
|
|
|
+
|
|
|
+ // FrontingSpec.Addresses may include a port; default to 443 if none.
|
|
|
+
|
|
|
+ if _, _, err := net.SplitHostPort(frontedMeekDialParams.FrontingDialAddress); err == nil {
|
|
|
+ frontedMeekDialParams.DialAddress = frontedMeekDialParams.FrontingDialAddress
|
|
|
+ } else {
|
|
|
+ frontedMeekDialParams.DialAddress = net.JoinHostPort(frontedMeekDialParams.FrontingDialAddress, "443")
|
|
|
+ }
|
|
|
+
|
|
|
+ // Determine and use the equivalent tunnel protocol for tactics
|
|
|
+ // selections. For example, for the broker transport FRONTED-HTTPS, use
|
|
|
+ // the tactics for FRONTED-MEEK-OSSH.
|
|
|
+
|
|
|
+ equivalentTunnelProtocol, err := protocol.EquivilentTunnelProtocol(frontedMeekDialParams.FrontingTransport)
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.Trace(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // SNI configuration
|
|
|
+ //
|
|
|
+ // For a FrontingSpec, an SNI value of "" indicates to disable/omit SNI, so
|
|
|
+ // never transform in that case.
|
|
|
+
|
|
|
+ if frontedMeekDialParams.SNIServerName != "" {
|
|
|
+ if p.WeightedCoinFlip(parameters.TransformHostNameProbability) {
|
|
|
+ frontedMeekDialParams.SNIServerName = selectHostName(equivalentTunnelProtocol, p)
|
|
|
+ frontedMeekDialParams.TransformedHostName = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // TLS configuration
|
|
|
+ //
|
|
|
+ // In the in-proxy case, the requireTLS13 flag is set to true, and
|
|
|
+ // requireTLS12SessionTickets to false, in order to use only modern TLS
|
|
|
+ // fingerprints which should support HTTP/2 in the ALPN.
|
|
|
+ //
|
|
|
+ // TODO: TLS padding
|
|
|
+
|
|
|
+ requireTLS12SessionTickets :=
|
|
|
+ !protocol.TunnelProtocolUsesInproxy(equivalentTunnelProtocol) &&
|
|
|
+ protocol.TunnelProtocolRequiresTLS12SessionTickets(
|
|
|
+ equivalentTunnelProtocol)
|
|
|
+
|
|
|
+ requireTLS13Support :=
|
|
|
+ protocol.TunnelProtocolUsesInproxy(equivalentTunnelProtocol) ||
|
|
|
+ protocol.TunnelProtocolRequiresTLS13Support(equivalentTunnelProtocol)
|
|
|
+ isFronted := true
|
|
|
+ frontedMeekDialParams.TLSProfile,
|
|
|
+ frontedMeekDialParams.TLSVersion,
|
|
|
+ frontedMeekDialParams.RandomizedTLSProfileSeed, err = SelectTLSProfile(
|
|
|
+ requireTLS12SessionTickets, requireTLS13Support, isFronted, frontedMeekDialParams.FrontingProviderID, p)
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.Trace(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if frontedMeekDialParams.TLSProfile == "" && (requireTLS12SessionTickets || requireTLS13Support) {
|
|
|
+ return nil, errors.TraceNew("required TLS profile not found")
|
|
|
+ }
|
|
|
+
|
|
|
+ frontedMeekDialParams.NoDefaultTLSSessionID = p.WeightedCoinFlip(
|
|
|
+ parameters.NoDefaultTLSSessionIDProbability)
|
|
|
+
|
|
|
+ if frontedMeekDialParams.SNIServerName != "" && net.ParseIP(frontedMeekDialParams.SNIServerName) == nil {
|
|
|
+ tlsFragmentorLimitProtocols := p.TunnelProtocols(parameters.TLSFragmentClientHelloLimitProtocols)
|
|
|
+ if len(tlsFragmentorLimitProtocols) == 0 || common.Contains(tlsFragmentorLimitProtocols, equivalentTunnelProtocol) {
|
|
|
+ frontedMeekDialParams.TLSFragmentClientHello = p.WeightedCoinFlip(parameters.TLSFragmentClientHelloProbability)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // User Agent configuration
|
|
|
+
|
|
|
+ dialCustomHeaders := makeDialCustomHeaders(config, p)
|
|
|
+ frontedMeekDialParams.SelectedUserAgent, frontedMeekDialParams.UserAgent = selectUserAgentIfUnset(p, dialCustomHeaders)
|
|
|
+
|
|
|
+ // Resolver configuration
|
|
|
+ //
|
|
|
+ // The custom resolver is wired up only when there is a domain to be
|
|
|
+ // resolved; GetMetrics will log resolver metrics when the resolver is set.
|
|
|
+
|
|
|
+ if net.ParseIP(frontedMeekDialParams.DialAddress) == nil {
|
|
|
+
|
|
|
+ resolver := config.GetResolver()
|
|
|
+ if resolver == nil {
|
|
|
+ return nil, errors.TraceNew("missing resolver")
|
|
|
+ }
|
|
|
+
|
|
|
+ frontedMeekDialParams.ResolveParameters, err = resolver.MakeResolveParameters(
|
|
|
+ p, frontedMeekDialParams.FrontingProviderID, frontedMeekDialParams.DialAddress)
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.Trace(err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if tunnel == nil {
|
|
|
+
|
|
|
+ // BPF configuration
|
|
|
+
|
|
|
+ if ClientBPFEnabled() &&
|
|
|
+ protocol.TunnelProtocolMayUseClientBPF(equivalentTunnelProtocol) {
|
|
|
+
|
|
|
+ if p.WeightedCoinFlip(parameters.BPFClientTCPProbability) {
|
|
|
+ frontedMeekDialParams.BPFProgramName = ""
|
|
|
+ frontedMeekDialParams.BPFProgramInstructions = nil
|
|
|
+ ok, name, rawInstructions := p.BPFProgram(parameters.BPFClientTCPProgram)
|
|
|
+ if ok {
|
|
|
+ frontedMeekDialParams.BPFProgramName = name
|
|
|
+ frontedMeekDialParams.BPFProgramInstructions = rawInstructions
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Fragmentor configuration
|
|
|
+
|
|
|
+ frontedMeekDialParams.FragmentorSeed, err = prng.NewSeed()
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.Trace(err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Initialize DialConfig to be passed to the corresponding dialers.
|
|
|
+
|
|
|
+ err = frontedMeekDialParams.prepareDialConfig(
|
|
|
+ config, p, tunnel, dialCustomHeaders, useDeviceBinder, skipVerify,
|
|
|
+ disableSystemRootCAs, payloadSecure)
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.Trace(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return &frontedMeekDialParams, nil
|
|
|
+}
|
|
|
+
|
|
|
+// prepareDialConfig is called for both new and replayed dial parameters.
|
|
|
+func (f *FrontedMeekDialParameters) prepareDialConfig(
|
|
|
+ config *Config,
|
|
|
+ p parameters.ParametersAccessor,
|
|
|
+ tunnel *Tunnel,
|
|
|
+ dialCustomHeaders http.Header,
|
|
|
+ useDeviceBinder,
|
|
|
+ skipVerify,
|
|
|
+ disableSystemRootCAs,
|
|
|
+ payloadSecure bool) error {
|
|
|
+
|
|
|
+ if !payloadSecure && (skipVerify || disableSystemRootCAs) {
|
|
|
+ return errors.TraceNew("cannot skip certificate verification if payload insecure")
|
|
|
+ }
|
|
|
+
|
|
|
+ equivilentTunnelProtocol, err := protocol.EquivilentTunnelProtocol(f.FrontingTransport)
|
|
|
+ if err != nil {
|
|
|
+ return errors.Trace(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Custom headers and User Agent
|
|
|
+
|
|
|
+ if dialCustomHeaders == nil {
|
|
|
+ dialCustomHeaders = makeDialCustomHeaders(config, p)
|
|
|
+ }
|
|
|
+ if f.SelectedUserAgent {
|
|
|
+ dialCustomHeaders.Set("User-Agent", f.UserAgent)
|
|
|
+ }
|
|
|
+
|
|
|
+ // Fragmentor
|
|
|
+
|
|
|
+ fragmentorConfig := fragmentor.NewUpstreamConfig(
|
|
|
+ p, equivilentTunnelProtocol, f.FragmentorSeed)
|
|
|
+
|
|
|
+ // Resolver
|
|
|
+ //
|
|
|
+ // DialConfig.ResolveIP is required and called even when the destination
|
|
|
+ // is an IP address.
|
|
|
+
|
|
|
+ resolver := config.GetResolver()
|
|
|
+ if resolver == nil {
|
|
|
+ return errors.TraceNew("missing resolver")
|
|
|
+ }
|
|
|
+
|
|
|
+ // DialConfig
|
|
|
+
|
|
|
+ f.resolvedIPAddress.Store("")
|
|
|
+
|
|
|
+ var resolveIP func(context.Context, string) ([]net.IP, error)
|
|
|
+ if tunnel != nil {
|
|
|
+ tunneledDialer := func(_, addr string) (net.Conn, error) {
|
|
|
+ // Set alwaysTunneled to ensure the http.Client traffic is always tunneled,
|
|
|
+ // even when split tunnel mode is enabled.
|
|
|
+ conn, _, err := tunnel.DialTCPChannel(addr, true, nil)
|
|
|
+ return conn, errors.Trace(err)
|
|
|
+ }
|
|
|
+ f.dialConfig = &DialConfig{
|
|
|
+ DiagnosticID: f.FrontingProviderID,
|
|
|
+ TrustedCACertificatesFilename: config.TrustedCACertificatesFilename,
|
|
|
+ CustomDialer: func(_ context.Context, _, addr string) (net.Conn, error) {
|
|
|
+ return tunneledDialer("", addr)
|
|
|
+ },
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ resolveIP = func(ctx context.Context, hostname string) ([]net.IP, error) {
|
|
|
+ IPs, err := UntunneledResolveIP(
|
|
|
+ ctx, config, resolver, hostname, f.FrontingProviderID)
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.Trace(err)
|
|
|
+ }
|
|
|
+ return IPs, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ var deviceBinder DeviceBinder
|
|
|
+ if useDeviceBinder {
|
|
|
+ deviceBinder = config.DeviceBinder
|
|
|
+ }
|
|
|
+
|
|
|
+ f.dialConfig = &DialConfig{
|
|
|
+ DiagnosticID: f.FrontingProviderID,
|
|
|
+ UpstreamProxyURL: config.UpstreamProxyURL,
|
|
|
+ CustomHeaders: dialCustomHeaders,
|
|
|
+ BPFProgramInstructions: f.BPFProgramInstructions,
|
|
|
+ TrustedCACertificatesFilename: config.TrustedCACertificatesFilename,
|
|
|
+ FragmentorConfig: fragmentorConfig,
|
|
|
+ DeviceBinder: deviceBinder,
|
|
|
+ IPv6Synthesizer: config.IPv6Synthesizer,
|
|
|
+ ResolveIP: resolveIP,
|
|
|
+ ResolvedIPCallback: func(IPAddress string) {
|
|
|
+ f.resolvedIPAddress.Store(IPAddress)
|
|
|
+ },
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // MeekDialConfig
|
|
|
+ //
|
|
|
+ // The broker round trips use MeekModeWrappedPlaintextRoundTrip without
|
|
|
+ // meek cookies, so meek obfuscation is not configured. The in-proxy
|
|
|
+ // broker session payloads have their own obfuscation layer.
|
|
|
+
|
|
|
+ // Note: if MeekModeRelay or MeekModeObfuscatedRoundTrip are supported in the
|
|
|
+ // future, set MeekObfuscatorPaddingSeed.
|
|
|
+ var meekMode MeekMode = MeekModePlaintextRoundTrip
|
|
|
+ if payloadSecure {
|
|
|
+ meekMode = MeekModeWrappedPlaintextRoundTrip
|
|
|
+ }
|
|
|
+
|
|
|
+ addPsiphonFrontingHeader := false
|
|
|
+ if f.FrontingProviderID != "" {
|
|
|
+ addPsiphonFrontingHeader = common.Contains(
|
|
|
+ p.LabeledTunnelProtocols(
|
|
|
+ parameters.AddFrontingProviderPsiphonFrontingHeader,
|
|
|
+ f.FrontingProviderID),
|
|
|
+ equivilentTunnelProtocol)
|
|
|
+ }
|
|
|
+
|
|
|
+ f.meekConfig = &MeekConfig{
|
|
|
+ DiagnosticID: f.FrontingProviderID,
|
|
|
+ Parameters: config.GetParameters(),
|
|
|
+ Mode: meekMode,
|
|
|
+ DialAddress: f.DialAddress,
|
|
|
+ TLSProfile: f.TLSProfile,
|
|
|
+ TLSFragmentClientHello: f.TLSFragmentClientHello,
|
|
|
+ NoDefaultTLSSessionID: f.NoDefaultTLSSessionID,
|
|
|
+ RandomizedTLSProfileSeed: f.RandomizedTLSProfileSeed,
|
|
|
+ SNIServerName: f.SNIServerName,
|
|
|
+ HostHeader: f.HostHeader,
|
|
|
+ TransformedHostName: f.TransformedHostName,
|
|
|
+ AddPsiphonFrontingHeader: addPsiphonFrontingHeader,
|
|
|
+ VerifyServerName: f.VerifyServerName,
|
|
|
+ VerifyPins: f.VerifyPins,
|
|
|
+ ClientTunnelProtocol: equivilentTunnelProtocol,
|
|
|
+ NetworkLatencyMultiplier: f.NetworkLatencyMultiplier,
|
|
|
+ AdditionalHeaders: config.MeekAdditionalHeaders,
|
|
|
+ // TODO: Change hard-coded session key be something like FrontingProviderID + BrokerID.
|
|
|
+ // This is necessary once longer-term TLS caches are added.
|
|
|
+ // The meek dial address, based on the fronting dial address returned by
|
|
|
+ // parameters.FrontingSpecs.SelectParameters has couple of issues. For some providers there's
|
|
|
+ // only a couple or even just one possible value, in other cases there are millions of possible values
|
|
|
+ // and cached values won't be used as often as they ought to be.
|
|
|
+ TLSClientSessionCache: common.WrapUtlsClientSessionCache(utls.NewLRUClientSessionCache(0), f.DialAddress),
|
|
|
+ }
|
|
|
+
|
|
|
+ if !skipVerify {
|
|
|
+ f.meekConfig.DisableSystemRootCAs = disableSystemRootCAs
|
|
|
+ if !f.meekConfig.DisableSystemRootCAs {
|
|
|
+ f.meekConfig.VerifyServerName = f.VerifyServerName
|
|
|
+ f.meekConfig.VerifyPins = f.VerifyPins
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ switch f.FrontingTransport {
|
|
|
+ case protocol.FRONTING_TRANSPORT_HTTPS:
|
|
|
+ f.meekConfig.UseHTTPS = true
|
|
|
+ case protocol.FRONTING_TRANSPORT_QUIC:
|
|
|
+ // TODO: configure QUIC tactics
|
|
|
+ f.meekConfig.UseQUIC = true
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// GetMetrics implements the common.MetricsSource interface and returns log
|
|
|
+// fields detailing the fronted meek dial parameters.
|
|
|
+func (meekDialParameters *FrontedMeekDialParameters) GetMetrics() common.LogFields {
|
|
|
+
|
|
|
+ logFields := make(common.LogFields)
|
|
|
+
|
|
|
+ logFields["fronting_provider_id"] = meekDialParameters.FrontingProviderID
|
|
|
+
|
|
|
+ if meekDialParameters.DialAddress != "" {
|
|
|
+ logFields["meek_dial_address"] = meekDialParameters.DialAddress
|
|
|
+ }
|
|
|
+
|
|
|
+ meekResolvedIPAddress := meekDialParameters.resolvedIPAddress.Load().(string)
|
|
|
+ if meekResolvedIPAddress != "" {
|
|
|
+ logFields["meek_resolved_ip_address"] = meekResolvedIPAddress
|
|
|
+ }
|
|
|
+
|
|
|
+ if meekDialParameters.SNIServerName != "" {
|
|
|
+ logFields["meek_sni_server_name"] = meekDialParameters.SNIServerName
|
|
|
+ }
|
|
|
+
|
|
|
+ if meekDialParameters.HostHeader != "" {
|
|
|
+ logFields["meek_host_header"] = meekDialParameters.HostHeader
|
|
|
+ }
|
|
|
+
|
|
|
+ transformedHostName := "0"
|
|
|
+ if meekDialParameters.TransformedHostName {
|
|
|
+ transformedHostName = "1"
|
|
|
+ }
|
|
|
+ logFields["meek_transformed_host_name"] = transformedHostName
|
|
|
+
|
|
|
+ if meekDialParameters.SelectedUserAgent {
|
|
|
+ logFields["user_agent"] = meekDialParameters.UserAgent
|
|
|
+ }
|
|
|
+
|
|
|
+ if meekDialParameters.FrontingTransport == protocol.FRONTING_TRANSPORT_HTTPS {
|
|
|
+
|
|
|
+ if meekDialParameters.TLSProfile != "" {
|
|
|
+ logFields["tls_profile"] = meekDialParameters.TLSProfile
|
|
|
+ }
|
|
|
+
|
|
|
+ if meekDialParameters.TLSVersion != "" {
|
|
|
+ logFields["tls_version"] =
|
|
|
+ getTLSVersionForMetrics(meekDialParameters.TLSVersion, meekDialParameters.NoDefaultTLSSessionID)
|
|
|
+ }
|
|
|
+
|
|
|
+ tlsFragmented := "0"
|
|
|
+ if meekDialParameters.TLSFragmentClientHello {
|
|
|
+ tlsFragmented = "1"
|
|
|
+ }
|
|
|
+ logFields["tls_fragmented"] = tlsFragmented
|
|
|
+ }
|
|
|
+
|
|
|
+ if meekDialParameters.BPFProgramName != "" {
|
|
|
+ logFields["client_bpf"] = meekDialParameters.BPFProgramName
|
|
|
+ }
|
|
|
+
|
|
|
+ if meekDialParameters.ResolveParameters != nil {
|
|
|
+
|
|
|
+ // See comment for dialParams.ResolveParameters handling in
|
|
|
+ // getBaseAPIParameters.
|
|
|
+
|
|
|
+ if meekDialParameters.ResolveParameters.PreresolvedIPAddress != "" {
|
|
|
+ dialDomain, _, _ := net.SplitHostPort(meekDialParameters.meekConfig.DialAddress)
|
|
|
+ if meekDialParameters.ResolveParameters.PreresolvedDomain == dialDomain {
|
|
|
+ logFields["dns_preresolved"] = meekDialParameters.ResolveParameters.PreresolvedIPAddress
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if meekDialParameters.ResolveParameters.PreferAlternateDNSServer {
|
|
|
+ logFields["dns_preferred"] = meekDialParameters.ResolveParameters.AlternateDNSServer
|
|
|
+ }
|
|
|
+
|
|
|
+ if meekDialParameters.ResolveParameters.ProtocolTransformName != "" {
|
|
|
+ logFields["dns_transform"] = meekDialParameters.ResolveParameters.ProtocolTransformName
|
|
|
+ }
|
|
|
+
|
|
|
+ logFields["dns_attempt"] = strconv.Itoa(
|
|
|
+ meekDialParameters.ResolveParameters.GetFirstAttemptWithAnswer())
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO: get fragmentor metrics, if any, from MeekConn.
|
|
|
+
|
|
|
+ return logFields
|
|
|
+}
|