inproxy

package
v0.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 20, 2024 License: GPL-3.0 Imports: 37 Imported by: 0

Documentation

Overview

Package inproxy enables 3rd party, ephemeral proxies to help Psiphon clients connect to the Psiphon network.

The in-proxy architecture is inspired by and similar to Tor's snowflake pluggable transport, https://snowflake.torproject.org/.

With in-proxy, Psiphon clients are matched with proxies by brokers run by the Psiphon network.

In addition to proxies in unblocked regions, proxies in blocked regions are supported, to facilitate the use cases such as a local region hop from a mobile ISP, where international traffic may be expensive and throttled, to a home ISP, which may be less restricted.

The proxy/server hop uses the full range of Psiphon tunnel protocols, providing blocking circumvention on the 2nd hop.

Proxies don't create Psiphon tunnels, they just relay either TCP or UDP flows from the client to the server, where those flows are Psiphon tunnel protocols. Proxies don't need to be upgraded in order to relay newer Psiphon tunnel protocols or protocol variants.

Proxies cannot see the client traffic within the relayed Psiphon tunnel. Brokers verify that client destinations are valid Psiphon servers only, so proxies cannot be misused for non-Psiphon relaying.

To limit the set of Psiphon servers that proxies can observe and enumerate, client destinations are limited to the set of servers specifically designated with in-proxy capabilities. This is enforced by the broker.

Proxies are compartmentalized in two ways; (1) personal proxies will use a personal compartment ID to limit access to clients run by users with whom the proxy operator has shared, out-of-band, a personal compartment ID, or access token; (2) common proxies will be assigned a common compartment ID by the Psiphon network to limit access to clients that have obtained the common compartment ID, or access token, from Psiphon through channels such as targeted tactics or embedded in OSLs.

Proxies are expected to be run for longer periods, on desktop computers. The in-proxy design does not currently support browser extension or website widget proxies.

The client/proxy hop uses WebRTC, with the broker playing the role of a WebRTC signaling server in addition to matching clients and proxies. Clients and proxies gather ICE candidates, including any host candidates, IPv4 or IPv6, as well as STUN server reflexive candidates. In addition, any available port mapping protocols -- UPnP-IGD, NAT-PMP, PCP -- are used to gather port mapping candidates, which are injected into ICE SDPs as host candidates. TURN candidates are not used.

NAT topology discovery is performed and metrics sent to broker to optimize utility and matching of proxies to clients. Mobile networks may be assumed to be CGNAT in case NAT discovery fails or is skipped. And, for mobile networks, there is an option to skip discovery and STUN for a faster dial.

The client-proxy is a WebRTC data channel; on the wire, it is DTLS, preceded by an ICE STUN packet. By default, WebRTC DTLS is configured to look like common browsers. In addition, the DTLS ClientHello can be randomized. Proxy endpoints are ephemeral, but if they were to be scanned or probed, the response should look like common WebRTC stacks that receive packets from invalid peers.

Clients and proxies connect to brokers via a domain fronting transport; the transport is abstracted and other channels may be provided. Within that transport, a Noise protocol framework session is established between clients/proxies and a broker, to ensure privacy, authentication, and replay defense between the end points; not even a domain fronting CDN can observe the transactions within a session. The session has an additional obfuscation layer that renders the messages as fully random, which may be suitable for encapsulating in plaintext transports; adds random padding; and detects replay of any message.

For clients and proxies, all broker and WebRTC dial parameters, including domain fronting, STUN server selection, NAT discovery behavior, timeouts, and so on are remotely configurable via Psiphon tactics. Callbacks facilitate replay of successful dial parameters for individual stages of a dial, including a successful broker connection, or a working STUN server.

For each proxied client tunnel, brokers use secure sessions to send the destination Psiphon server a message indicating the proxy ID that's relaying the client's traffic, the original client IP, and additional metrics to be logged with the server_tunnel log for the tunnel. Neither a client nor a proxy is trusted to report the original client IP or the proxy ID.

Instead of having the broker connect out to Psiphon servers, and trying to synchronize reliable arrival of these messages, the broker uses the client to relay secure session packets -- the message, preceded by a session handshake if required -- inline, in the client/broker and client/server tunnel connections. These session packets piggyback on top of client/broker and client/server round trips that happen anyway, including the Psiphon API handshake.

Psiphon servers with in-proxy capabilities should be configured, on in-proxy listeners, to require receipt of this broker message before finalizing traffic rules, issuing tactics, issuing OSL progress, or allowing traffic tunneling. The original client IP reported by the broker should be used for all client GeoIP policy decisions and logging.

The proxy ID corresponds to the proxy's secure session public key; the proxy proves possession of the corresponding private key in the session handshake. Proxy IDs are not revealed to clients; only to brokers and Psiphon servers. A proxy may maintain a long-term key pair and corresponding proxy ID, and that may be used by Psiphon to assign reputation to well-performing proxies or to issue rewards for proxies.

Each secure session public key is an Ed25519 public key. This public key is used for signatures, including the session reset token in the session protocol. This signing key may also be used, externally, in a challenge/response registration process where a proxy operator can demonstrate ownership of a proxy public key and its corresponding proxy ID. For use in ECDH in the Noise protocol, the Ed25519 public key is converted to the corresponding, unique Curve25519 public key.

Logged proxy ID values will be the Curve25519 representation of the public key. Since Curve25519 public keys don't uniquely map back to Ed25519 public keys, any external proxy registration system should store the Ed25519 public key and derive the corresponding Curve25519 when mapping server tunnel proxy IDs back to the Ed25519 proxy public key.

The proxy is designed to be bundled with the tunnel-core client, run optionally, and integrated with its tactics, data store, and logging. The broker is designed to be bundled with the Psiphon server, psiphond, and, like tactics requests, run under MeekServer; and use the tactics, psinet database, GeoIP services, and logging services provided by psiphond.

The build tag PSIPHON_ENABLE_INPROXY must be specified in order to enable in-proxy components. Without this build tag, the components are disabled and larger dependencies are not referenced and excluded from builds.

  • Copyright (c) 2023, Psiphon Inc.
  • All rights reserved. *
  • This program is free software: you can redistribute it and/or modify
  • it under the terms of the GNU General Public License as published by
  • the Free Software Foundation, either version 3 of the License, or
  • (at your option) any later version. *
  • This program is distributed in the hope that it will be useful,
  • but WITHOUT ANY WARRANTY; without even the implied warranty of
  • MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  • GNU General Public License for more details. *
  • You should have received a copy of the GNU General Public License
  • along with this program. If not, see <http://www.gnu.org/licenses/>. *

Index

Constants

View Source
const (
	ProxyProtocolVersion1 = 1
	MaxCompartmentIDs     = 10
)
View Source
const (

	// BrokerMaxRequestBodySize is the maximum request size, that should be
	// enforced by the provided broker transport.
	BrokerMaxRequestBodySize = 65536

	// BrokerEndPointName is the standard name for referencing an endpoint
	// that services broker requests.
	BrokerEndPointName = "inproxy-broker"
)
View Source
const (
	SessionProtocolName     = "psiphon-inproxy-session"
	SessionProtocolVersion1 = 1
)
View Source
const MaxRelayRoundTrips = 10

MaxRelayRoundTrips is a sanity/anti-DoS check against clients that attempt to relay more packets than are required for both a session handshake and application-level request round trip.

Variables

View Source
var (
	NATTypeUnknown = MakeNATType(NATMappingUnknown, NATFilteringUnknown)

	// NATTypePortMapping is a pseudo NATType, used in matching, that
	// represents the relevant NAT behavior of a port mapping (e.g., UPnP-IGD).
	NATTypePortMapping = MakeNATType(NATMappingEndpointIndependent, NATFilteringEndpointIndependent)

	// NATTypeMobileNetwork is a pseudo NATType, usied in matching, that
	// represents the assumed and relevent NAT behavior of clients on mobile
	// networks, presumed to be behind CGNAT when they report NATTypeUnknown.
	NATTypeMobileNetwork = MakeNATType(NATMappingAddressPortDependent, NATFilteringAddressPortDependent)

	// NATTypeNone and the following NATType constants are used in testing.
	// They are not entirely precise (a symmetric NAT may have a different
	// mix of mapping and filtering values). The matching logic does not use
	// specific NAT type definitions and instead considers the reported
	// mapping and filtering values.
	NATTypeNone               = MakeNATType(NATMappingEndpointIndependent, NATFilteringEndpointIndependent)
	NATTypeFullCone           = MakeNATType(NATMappingEndpointIndependent, NATFilteringEndpointIndependent)
	NATTypeRestrictedCone     = MakeNATType(NATMappingEndpointIndependent, NATFilteringAddressDependent)
	NATTypePortRestrictedCone = MakeNATType(NATMappingEndpointIndependent, NATFilteringAddressPortDependent)
	NATTypeSymmetric          = MakeNATType(NATMappingAddressPortDependent, NATFilteringAddressPortDependent)
)

Functions

func Enabled

func Enabled() bool

Enabled indicates if in-proxy functionality is enabled.

func GetAllowBogonWebRTCConnections

func GetAllowBogonWebRTCConnections() bool

func GetAllowCommonASNMatching

func GetAllowCommonASNMatching() bool

func HaveCommonIDs

func HaveCommonIDs(a, b []ID) bool

HaveCommonIDs indicates whether two lists of IDs have a common entry.

func MarshalBrokerServerReport

func MarshalBrokerServerReport(request *BrokerServerReport) ([]byte, error)

func MarshalClientOfferRequest

func MarshalClientOfferRequest(request *ClientOfferRequest) ([]byte, error)

func MarshalClientOfferResponse

func MarshalClientOfferResponse(response *ClientOfferResponse) ([]byte, error)

func MarshalClientRelayedPacketRequest

func MarshalClientRelayedPacketRequest(request *ClientRelayedPacketRequest) ([]byte, error)

func MarshalClientRelayedPacketResponse

func MarshalClientRelayedPacketResponse(response *ClientRelayedPacketResponse) ([]byte, error)

func MarshalProxyAnnounceRequest

func MarshalProxyAnnounceRequest(request *ProxyAnnounceRequest) ([]byte, error)

func MarshalProxyAnnounceResponse

func MarshalProxyAnnounceResponse(response *ProxyAnnounceResponse) ([]byte, error)

func MarshalProxyAnswerRequest

func MarshalProxyAnswerRequest(request *ProxyAnswerRequest) ([]byte, error)

func MarshalProxyAnswerResponse

func MarshalProxyAnswerResponse(response *ProxyAnswerResponse) ([]byte, error)

func NATDiscover

func NATDiscover(
	ctx context.Context,
	config *NATDiscoverConfig)

NATDiscover runs NAT type and port mapping type discovery operations.

Successfuly results are delivered to NATDiscoverConfig.WebRTCDialCoordinator callbacks, SetNATType and SetPortMappingTypes, which should cache results associated with the current network, by network ID.

NAT discovery will invoke WebRTCDialCoordinator callbacks STUNServerAddressSucceeded and STUNServerAddressFailed, which may be used to mark or unmark STUN servers for replay.

func SetAllowBogonWebRTCConnections

func SetAllowBogonWebRTCConnections(allow bool)

SetAllowBogonWebRTCConnections configures whether to allow bogon ICE candidates in WebRTC session descriptions. This included loopback and private network candidates. By default, bogon addresses are exclude as they are not expected to be useful and may expose private network information. SetAllowBogonWebRTCConnections is for end-to-end testing on a single host, and should be used only for testing purposes.

func SetAllowCommonASNMatching

func SetAllowCommonASNMatching(allow bool)

SetAllowCommonASNMatching configures whether to allow matching proxies and clients with the same GeoIP country and ASN. This matching is always permitted for matching personal compartment IDs, but for common compartment IDs, these matches are not allowed as they are not expected to be useful. SetAllowCommonASNMatching is for end-to-end testing on a single host, and should be used only for testing purposes.

Types

type ActivityUpdater

type ActivityUpdater func(
	connectingClients int32,
	connectedClients int32,
	bytesUp int64,
	bytesDown int64,
	bytesDuration time.Duration)

ActivityUpdater is a callback that is invoked when clients connect and disconnect and periodically with data transfer updates (unless idle). This callback may be used to update an activity UI. This callback should post this data to another thread or handler and return immediately and not block on UI updates.

type Broker

type Broker struct {
	// contains filtered or unexported fields
}

Broker is the in-proxy broker component, which matches clients and proxies and provides WebRTC signaling functionalty.

Both clients and proxies send requests to the broker to obtain matches and exchange WebRTC SDPs. Broker does not implement a transport or obfuscation layer; instead that is provided by the HandleSessionPacket caller. A typical implementation would provide a domain fronted web server which runs a Broker and calls Broker.HandleSessionPacket to handle web requests encapsulating secure session packets.

func NewBroker

func NewBroker(config *BrokerConfig) (*Broker, error)

NewBroker initializes a new Broker.

func (*Broker) HandleSessionPacket

func (b *Broker) HandleSessionPacket(
	ctx context.Context,
	extendTransportTimeout ExtendTransportTimeout,
	transportLogFields common.LogFields,
	brokerClientIP string,
	geoIPData common.GeoIPData,
	inPacket []byte) ([]byte, error)

HandleSessionPacket handles a session packet from a client or proxy and provides a response packet. The packet is part of a secure session and may be a session handshake message, an expired session reset token, or a session-wrapped request payload. Request payloads are routed to API request endpoints.

The caller is expected to provide a transport obfuscation layer, such as domain fronted HTTPs. The session has an obfuscation layer that ensures that packets are fully random, randomly padded, and cannot be replayed. This makes session packets suitable to embed as plaintext in some transports.

The caller is responsible for rate limiting and enforcing timeouts and maximum payload size checks.

Secure sessions support multiplexing concurrent requests, as long as the provided transport, for example HTTP/2, supports this as well.

The input ctx should be canceled if the client/proxy disconnects from the transport while HandleSessionPacket is running, since long-polling proxy announcement requests will otherwise remain blocked until eventual timeout; net/http does this.

When HandleSessionPacket returns an error, the transport provider should apply anti-probing mechanisms, as the client/proxy may be a prober or scanner.

func (*Broker) SetCommonCompartmentIDs

func (b *Broker) SetCommonCompartmentIDs(commonCompartmentIDs []ID) error

SetCommonCompartmentIDs sets a new list of common compartment IDs, replacing the previous configuration.

func (*Broker) SetLimits

func (b *Broker) SetLimits(
	matcherAnnouncementLimitEntryCount int,
	matcherAnnouncementRateLimitQuantity int,
	matcherAnnouncementRateLimitInterval time.Duration,
	matcherAnnouncementNonlimitedProxyIDs []ID,
	matcherOfferLimitEntryCount int,
	matcherOfferRateLimitQuantity int,
	matcherOfferRateLimitInterval time.Duration,
	maxCompartmentIDs int)

SetLimits sets new queue limit values, replacing the previous configuration. New limits are only partially applied to existing queue states; see Matcher.SetLimits.

func (*Broker) SetTimeouts

func (b *Broker) SetTimeouts(
	proxyAnnounceTimeout time.Duration,
	clientOfferTimeout time.Duration,
	pendingServerReportsTTL time.Duration)

SetTimeouts sets new timeout values, replacing the previous configuration. New timeout values do not apply to currently active announcement or offer requests.

func (*Broker) Start

func (b *Broker) Start() error

func (*Broker) Stop

func (b *Broker) Stop()

type BrokerClient

type BrokerClient struct {
	// contains filtered or unexported fields
}

BrokerClient is used to make requests to a broker.

Each BrokerClient maintains a secure broker session. A BrokerClient and its session may be used for multiple concurrent requests. Session key material is provided by BrokerDialCoordinator and must remain static for the lifetime of the BrokerClient.

Round trips between the BrokerClient and broker are provided by BrokerClientRoundTripper from BrokerDialCoordinator. The RoundTripper must maintain the association between a request payload and the corresponding response payload. The canonical RoundTripper is an HTTP client, with HTTP/2 or HTTP/3 used to multiplex concurrent requests.

When the BrokerDialCoordinator BrokerClientRoundTripperSucceeded call back is invoked, the RoundTripper provider may mark the RoundTripper dial properties for replay.

When the BrokerDialCoordinator BrokerClientRoundTripperFailed call back is invoked, the RoundTripper provider should clear any replay state and also create a new RoundTripper to be returned from BrokerClientRoundTripper.

BrokerClient does not have a Close operation. The user should close the provided RoundTripper as appropriate.

The secure session layer includes obfuscation that provides random padding and uniformly random payload content. The RoundTripper is expected to add its own obfuscation layer; for example, domain fronting.

func NewBrokerClient

func NewBrokerClient(coordinator BrokerDialCoordinator) (*BrokerClient, error)

NewBrokerClient initializes a new BrokerClient with the provided BrokerDialCoordinator.

func (*BrokerClient) ClientOffer

func (b *BrokerClient) ClientOffer(
	ctx context.Context,
	request *ClientOfferRequest) (*ClientOfferResponse, error)

ClientOffer sends a ClientOffer request and returns the response.

func (*BrokerClient) ClientRelayedPacket

func (b *BrokerClient) ClientRelayedPacket(
	ctx context.Context,
	request *ClientRelayedPacketRequest) (*ClientRelayedPacketResponse, error)

ClientRelayedPacket sends a ClientRelayedPacket request and returns the response.

func (*BrokerClient) GetBrokerDialCoordinator

func (b *BrokerClient) GetBrokerDialCoordinator() BrokerDialCoordinator

GetBrokerDialCoordinator returns the BrokerDialCoordinator associated with the BrokerClient.

func (*BrokerClient) ProxyAnnounce

func (b *BrokerClient) ProxyAnnounce(
	ctx context.Context,
	requestDelay time.Duration,
	request *ProxyAnnounceRequest) (*ProxyAnnounceResponse, error)

ProxyAnnounce sends a ProxyAnnounce request and returns the response.

func (*BrokerClient) ProxyAnswer

func (b *BrokerClient) ProxyAnswer(
	ctx context.Context,
	request *ProxyAnswerRequest) (*ProxyAnswerResponse, error)

ProxyAnswer sends a ProxyAnswer request and returns the response.

type BrokerConfig

type BrokerConfig struct {

	// Logger is used to log events.
	Logger common.Logger

	// CommonCompartmentIDs is a list of common compartment IDs to apply to
	// proxies that announce without personal compartment ID. Common
	// compartment IDs are managed by Psiphon and distributed to clients via
	// tactics or embedded in OSLs. Clients must supply a valid compartment
	// ID to match with a proxy.
	//
	// A BrokerConfig must supply at least one compartment ID, or
	// SetCompartmentIDs must be called with at least one compartment ID
	// before calling Start.
	//
	// When only one, single common compartment ID is configured, it can serve
	// as an (obfuscation) secret that clients must obtain, via tactics, to
	// enable in-proxy participation.
	CommonCompartmentIDs []ID

	// AllowProxy is a callback which can indicate whether a proxy with the
	// given GeoIP data is allowed to match with common compartment ID
	// clients. Proxies with personal compartment IDs are always allowed.
	AllowProxy func(common.GeoIPData) bool

	// AllowClient is a callback which can indicate whether a client with the
	// given GeoIP data is allowed to match with common compartment ID
	// proxies. Clients are always allowed to match based on personal
	// compartment ID.
	AllowClient func(common.GeoIPData) bool

	// AllowDomainFrontedDestinations is a callback which can indicate whether
	// a client with the given GeoIP data is allowed to specify a proxied
	// destination for a domain fronted protocol. When false, only direct
	// address destinations are allowed.
	//
	// While tactics may may be set to instruct clients to use only direct
	// server tunnel protocols, with IP address destinations, this callback
	// adds server-side enforcement.
	AllowDomainFrontedDestinations func(common.GeoIPData) bool

	// LookupGeoIP provides GeoIP lookup service.
	LookupGeoIP LookupGeoIP

	// APIParameterValidator is a callback that validates base API metrics.
	APIParameterValidator common.APIParameterValidator

	// APIParameterValidator is a callback that formats base API metrics.
	APIParameterLogFieldFormatter common.APIParameterLogFieldFormatter

	// GetTactics provides a tactics lookup service.
	GetTactics GetTactics

	// IsValidServerEntryTag is a callback which checks if the specified
	// server entry tag is on the list of valid and active Psiphon server
	// entry tags.
	IsValidServerEntryTag func(serverEntryTag string) bool

	// PrivateKey is the broker's secure session long term private key.
	PrivateKey SessionPrivateKey

	// ObfuscationRootSecret broker's secure session long term obfuscation key.
	ObfuscationRootSecret ObfuscationSecret

	// ServerEntrySignaturePublicKey is the key used to verify Psiphon server
	// entry signatures.
	ServerEntrySignaturePublicKey string

	// These timeout parameters may be used to override defaults.
	ProxyAnnounceTimeout    time.Duration
	ClientOfferTimeout      time.Duration
	PendingServerReportsTTL time.Duration

	// Announcement queue limit configuration.
	MatcherAnnouncementLimitEntryCount    int
	MatcherAnnouncementRateLimitQuantity  int
	MatcherAnnouncementRateLimitInterval  time.Duration
	MatcherAnnouncementNonlimitedProxyIDs []ID

	// Offer queue limit configuration.
	MatcherOfferLimitEntryCount   int
	MatcherOfferRateLimitQuantity int
	MatcherOfferRateLimitInterval time.Duration

	// MaxCompartmentIDs specifies the maximum number of compartment IDs that
	// can be included, per list, in one request. If 0, the value
	// MaxCompartmentIDs is used.
	MaxCompartmentIDs int
}

BrokerConfig specifies the configuration for a Broker.

type BrokerDialCoordinator

type BrokerDialCoordinator interface {

	// Returns the network ID for the network this BrokerDialCoordinator is
	// associated with. For a single BrokerDialCoordinator, the NetworkID value
	// should not change. Replay-facilitating calls, Succeeded/Failed, all
	// assume the network and network ID remain static. The network ID value
	// is used by in-proxy dials to track internal state that depends on the
	// current network; this includes the port mapping types supported by the
	// network.
	NetworkID() string

	// Returns the network type for the current network, or NetworkTypeUnknown
	// if unknown.
	NetworkType() NetworkType

	// CommonCompartmentIDs is the list of common, Psiphon-managed, in-proxy
	// compartment IDs known to a client. These IDs are delivered through
	// tactics, or embedded in OSLs.
	//
	// At most MaxCompartmentIDs may be sent to a broker; if necessary, the
	// provider may return a subset of known compartment IDs and replay when
	// the overall dial is a success; and/or retain only the most recently
	// discovered compartment IDs.
	//
	// CommonCompartmentIDs is not called for proxies.
	CommonCompartmentIDs() []ID

	// PersonalCompartmentIDs are compartment IDs distributed from proxy
	// operators to client users out-of-band and provide optional access
	// control. For example, a proxy operator may want to provide access only
	// to certain users, and/or users want to use only a proxy run by a
	// certain operator.
	//
	// At most MaxCompartmentIDs may be sent to a broker; for typical use
	// cases, both clients and proxies will specify a single personal
	// compartment ID.
	PersonalCompartmentIDs() []ID

	// BrokerClientPrivateKey is the client or proxy's private key to be used
	// in the secure session established with a broker. Clients should
	// generate ephemeral keys; this is done automatically when a zero-value
	// SessionPrivateKey is returned. Proxies may generate, persist, and
	// long-lived keys to enable traffic attribution to a proxy, identified
	// by a proxy ID, the corresponding public key.
	BrokerClientPrivateKey() SessionPrivateKey

	// BrokerPublicKey is the public key for the broker selected by the
	// provider and reachable via BrokerClientRoundTripper. The broker is
	// authenticated in the secure session.
	BrokerPublicKey() SessionPublicKey

	// BrokerRootObfuscationSecret is the root obfuscation secret for the
	// broker and used in the secure session.
	BrokerRootObfuscationSecret() ObfuscationSecret

	// BrokerClientRoundTripper returns a RoundTripper to use for broker
	// requests. The provider handles selecting a broker and broker
	// addressing, as well as providing a round trip network transport with
	// blocking circumvention capabilities. A typical implementation is
	// domain fronted HTTPS. The RoundTripper should offer persistent network
	// connections and request multiplexing, for example with HTTP/2, so that
	// a single connection can be used for many concurrent requests.
	//
	// Clients and proxies make round trips to establish a secure session with
	// the broker, on top of the provided transport, and to exchange API
	// requests with the broker.
	//
	// The implementation must return a RoundTripper connecting to the same
	// broker for every call, as multiple-request sequences such as
	// ProxyAnnounce and ProxyAnswer depend on broker state.
	BrokerClientRoundTripper() (RoundTripper, error)

	// BrokerClientRoundTripperSucceeded is called after a successful round
	// trip using the specified RoundTripper. This signal is used to set
	// replay for the round tripper's successful dial parameters.
	// BrokerClientRoundTripperSucceeded is called once per successful round
	// trip; the provider can choose to set replay only once.
	BrokerClientRoundTripperSucceeded(roundTripper RoundTripper)

	// BrokerClientRoundTripperSucceeded is called after a failed round trip
	// using the specified RoundTripper. This signal is used to clear replay
	// for the round tripper's unsuccessful dial parameters. The provider
	// will arrange for a new RoundTripper to be returned from the next
	// BrokerClientRoundTripper call, discarding the current RoundTripper
	// after closing its network resources.
	BrokerClientRoundTripperFailed(roundTripper RoundTripper)

	SessionHandshakeRoundTripTimeout() time.Duration
	AnnounceRequestTimeout() time.Duration
	AnnounceDelay() time.Duration
	AnnounceDelayJitter() float64
	AnswerRequestTimeout() time.Duration
	OfferRequestTimeout() time.Duration
	OfferRetryDelay() time.Duration
	OfferRetryJitter() float64
	RelayedPacketRequestTimeout() time.Duration
}

BrokerDialCoordinator provides in-proxy dial parameters and configuration, used by both clients and proxies, and an interface for signaling when parameters are successful or not, to facilitate replay of successful parameters.

Each BrokerDialCoordinator should provide values selected in the context of a single network, as identified by a network ID. A distinct BrokerDialCoordinator should be created for each in-proxy broker dial, with new or replayed parameters selected as appropriate. Multiple in-proxy client dials and/or proxy runs may share a single BrokerDialCoordinator, reducing round trips required to make broker requests. A BrokerDialCoordinator implementation must be safe for concurrent calls.

The Psiphon client is expected to create a new BrokerDialCoordinator for use by in-proxy clients when the underlying network changes and tunnels are redialed. Similarly, in-proxy proxies should be restarted with a new BrokerDialCoordinator when the underlying network changes.

type BrokerServerReport

type BrokerServerReport struct {
	ProxyID                     ID               `cbor:"1,keyasint,omitempty"`
	ConnectionID                ID               `cbor:"2,keyasint,omitempty"`
	MatchedCommonCompartments   bool             `cbor:"3,keyasint,omitempty"`
	MatchedPersonalCompartments bool             `cbor:"4,keyasint,omitempty"`
	ProxyNATType                NATType          `cbor:"5,keyasint,omitempty"`
	ProxyPortMappingTypes       PortMappingTypes `cbor:"6,keyasint,omitempty"`
	ClientNATType               NATType          `cbor:"7,keyasint,omitempty"`
	ClientPortMappingTypes      PortMappingTypes `cbor:"8,keyasint,omitempty"`
	ClientIP                    string           `cbor:"9,keyasint,omitempty"`
	ProxyIP                     string           `cbor:"10,keyasint,omitempty"`
}

BrokerServerReport is a one-way API call sent from a broker to a Psiphon server. This delivers, to the server, information that neither the client nor the proxy is trusted to report. ProxyID is the proxy ID to be logged with server_tunnel to attribute traffic to a specific proxy. ClientIP is the original client IP as seen by the broker; this is the IP value to be used in GeoIP-related operations including traffic rules, tactics, and OSL progress. ProxyIP is the proxy IP as seen by the broker; this value should match the Psiphon's server observed client IP. Additional fields are metrics to be logged with server_tunnel.

Using a one-way message here means that, once a broker/server session is established, the entire relay can be encasulated in a single additional field sent in the Psiphon API handshake. This minimizes observable and potentially fingerprintable traffic flows as the client does not need to relay any further session packets before starting the tunnel. The trade-off is that the broker doesn't get an indication from the server that the message was accepted or rejects and cannot directly, in real time log any tunnel error associated with the server rejecting the message, or log that the relay was completed successfully. These events can be logged on the server and logs reconciled using the in-proxy Connection ID.

func UnmarshalBrokerServerReport

func UnmarshalBrokerServerReport(payload []byte) (*BrokerServerReport, error)

func (*BrokerServerReport) ValidateAndGetLogFields

func (request *BrokerServerReport) ValidateAndGetLogFields() (common.LogFields, error)

ValidateAndGetLogFields validates the BrokerServerReport and returns common.LogFields for logging.

type ClientConfig

type ClientConfig struct {

	// Logger is used to log events.
	Logger common.Logger

	// EnableWebRTCDebugLogging indicates whether to emit WebRTC debug logs.
	EnableWebRTCDebugLogging bool

	// BaseAPIParameters should be populated with Psiphon handshake metrics
	// parameters. These will be sent to and logger by the broker.
	BaseAPIParameters common.APIParameters

	// BrokerClient is the BrokerClient to use for broker API calls. The
	// BrokerClient may be shared with other client dials, allowing for
	// connection and session reuse.
	BrokerClient *BrokerClient

	// WebRTCDialCoordinator specifies specific WebRTC dial strategies and
	// settings; WebRTCDialCoordinator also facilities dial replay by
	// receiving callbacks when individual dial steps succeed or fail.
	WebRTCDialCoordinator WebRTCDialCoordinator

	// ReliableTransport specifies whether to use reliable delivery with the
	// underlying WebRTC DataChannel that relays the ClientConn traffic. When
	// using a ClientConn to proxy traffic that expects reliable delivery, as
	// if the physical network protocol were TCP, specify true. When using a
	// ClientConn to proxy traffic that expects unreliable delivery, such as
	// QUIC protocols expecting the physical network protocol UDP, specify
	// false.
	ReliableTransport bool

	// DialNetworkProtocol specifies whether the in-proxy will relay TCP or UDP
	// traffic.
	DialNetworkProtocol NetworkProtocol

	// DialAddress is the host:port destination network address the in-proxy
	// will relay traffic to.
	DialAddress string

	// RemoteAddrOverride, when specified, is the address to be returned by
	// ClientConn.RemoteAddr. When not specified, ClientConn.RemoteAddr
	// returns a zero-value address.
	RemoteAddrOverride string

	// PackedDestinationServerEntry is a signed Psiphon server entry
	// corresponding to the destination dial address. This signed server
	// entry is sent to the broker, which will use it to validate that the
	// server is a valid in-proxy destination.
	//
	// The expected format is CBOR-encoded protoco.PackedServerEntryFields,
	// with the caller invoking  ServerEntryFields.RemoveUnsignedFields to
	// prune local, unnsigned fields before sending.
	PackedDestinationServerEntry []byte
}

ClientConfig specifies the configuration for a ClientConn dial.

type ClientConn

type ClientConn struct {
	// contains filtered or unexported fields
}

ClientConn is a network connection to an in-proxy, which is relayed to a Psiphon server destination. Psiphon clients use a ClientConn in place of a physical TCP or UDP socket connection, passing the ClientConn into tunnel protocol dials. ClientConn implements both net.Conn and net.PacketConn, with net.PacketConn's ReadFrom/WriteTo behaving as if connected to the initial dial address.

func DialClient

func DialClient(
	ctx context.Context,
	config *ClientConfig) (retConn *ClientConn, retErr error)

DialClient establishes an in-proxy connection for relaying traffic to the specified destination. DialClient first contacts the broker and initiates an in-proxy pairing. config.BrokerClient may be shared by multiple dials, and may have a preexisting connection and session with the broker.

func (*ClientConn) Close

func (conn *ClientConn) Close() error

func (*ClientConn) GetConnectionID

func (conn *ClientConn) GetConnectionID() ID

GetConnectionID returns the in-proxy connection ID, which the client should include with its Psiphon handshake parameters.

func (*ClientConn) GetMetrics

func (conn *ClientConn) GetMetrics() common.LogFields

GetMetrics implements the common.MetricsSource interface.

func (*ClientConn) InitialRelayPacket

func (conn *ClientConn) InitialRelayPacket() []byte

InitialRelayPacket returns the initial packet in the broker->server messaging session. The client must relay these packets to facilitate this message exchange. Session security ensures clients cannot decrypt, modify, or replay these session packets. The Psiphon client will sent the initial packet as a parameter in the Psiphon server handshake request.

func (*ClientConn) IsClosed

func (conn *ClientConn) IsClosed() bool

func (*ClientConn) LocalAddr

func (conn *ClientConn) LocalAddr() net.Addr

func (*ClientConn) Read

func (conn *ClientConn) Read(p []byte) (int, error)

func (*ClientConn) ReadFrom

func (conn *ClientConn) ReadFrom(b []byte) (int, net.Addr, error)

func (*ClientConn) RelayPacket

func (conn *ClientConn) RelayPacket(
	ctx context.Context, in []byte) ([]byte, error)

RelayPacket takes any server->broker messaging session packets the client receives and relays them back to the broker. RelayPacket returns the next broker->server packet, if any, or nil when the message exchange is complete. Psiphon clients receive a server->broker packet in the Psiphon server handshake response and exchange additional packets in a post-handshake Psiphon server request.

If RelayPacket fails, the client should close the ClientConn and redial.

func (*ClientConn) RemoteAddr

func (conn *ClientConn) RemoteAddr() net.Addr

func (*ClientConn) SetDeadline

func (conn *ClientConn) SetDeadline(t time.Time) error

func (*ClientConn) SetReadDeadline

func (conn *ClientConn) SetReadDeadline(t time.Time) error

func (*ClientConn) SetWriteDeadline

func (conn *ClientConn) SetWriteDeadline(t time.Time) error

func (*ClientConn) Write

func (conn *ClientConn) Write(p []byte) (int, error)

Write relays p through the in-proxy connection. len(p) should be under 32K.

func (*ClientConn) WriteTo

func (conn *ClientConn) WriteTo(b []byte, _ net.Addr) (int, error)

type ClientMetrics

type ClientMetrics struct {
	BaseAPIParameters    protocol.PackedAPIParameters `cbor:"1,keyasint,omitempty"`
	ProxyProtocolVersion int32                        `cbor:"2,keyasint,omitempty"`
	NATType              NATType                      `cbor:"3,keyasint,omitempty"`
	PortMappingTypes     PortMappingTypes             `cbor:"4,keyasint,omitempty"`
}

ClientMetrics are network topolology metrics provided by a client to a broker. The broker uses this information when matching proxies and clients.

func (*ClientMetrics) ValidateAndGetLogFields

func (metrics *ClientMetrics) ValidateAndGetLogFields(
	baseAPIParameterValidator common.APIParameterValidator,
	formatter common.APIParameterLogFieldFormatter,
	geoIPData common.GeoIPData) (common.LogFields, error)

ValidateAndGetLogFields validates the ClientMetrics and returns common.LogFields for logging.

type ClientOfferRequest

type ClientOfferRequest struct {
	Metrics                      *ClientMetrics                       `cbor:"1,keyasint,omitempty"`
	CommonCompartmentIDs         []ID                                 `cbor:"2,keyasint,omitempty"`
	PersonalCompartmentIDs       []ID                                 `cbor:"3,keyasint,omitempty"`
	ClientOfferSDP               WebRTCSessionDescription             `cbor:"4,keyasint,omitempty"`
	ICECandidateTypes            ICECandidateTypes                    `cbor:"5,keyasint,omitempty"`
	ClientRootObfuscationSecret  ObfuscationSecret                    `cbor:"6,keyasint,omitempty"`
	DoDTLSRandomization          bool                                 `cbor:"7,keyasint,omitempty"`
	TrafficShapingParameters     *DataChannelTrafficShapingParameters `cbor:"8,keyasint,omitempty"`
	PackedDestinationServerEntry []byte                               `cbor:"9,keyasint,omitempty"`
	NetworkProtocol              NetworkProtocol                      `cbor:"10,keyasint,omitempty"`
	DestinationAddress           string                               `cbor:"11,keyasint,omitempty"`
}

ClientOfferRequest is an API request sent from a client to a broker, requesting a proxy connection. The client sends its WebRTC offer SDP with this request.

Clients specify known compartment IDs and are matched with proxies in those compartments. CommonCompartmentIDs are comparment IDs managed by Psiphon and revealed through tactics or bundled with server lists. PersonalCompartmentIDs are compartment IDs shared privately between users, out-of-band.

ClientRootObfuscationSecret is generated (or replayed) by the client and sent to the proxy and used to drive obfuscation operations.

To specify the Psiphon server it wishes to proxy to, the client sends the full, digitally signed Psiphon server entry to the broker and also the specific dial address that it has selected for that server. The broker validates the server entry signature, the server in-proxy capability, and that the dial address corresponds to the network protocol, IP address or domain, and destination port for a valid Psiphon tunnel protocol run by the specified server entry.

func UnmarshalClientOfferRequest

func UnmarshalClientOfferRequest(payload []byte) (*ClientOfferRequest, error)

func (*ClientOfferRequest) ValidateAndGetLogFields

func (request *ClientOfferRequest) ValidateAndGetLogFields(
	maxCompartmentIDs int,
	lookupGeoIP LookupGeoIP,
	baseAPIParameterValidator common.APIParameterValidator,
	formatter common.APIParameterLogFieldFormatter,
	geoIPData common.GeoIPData) (common.LogFields, error)

ValidateAndGetLogFields validates the ClientOfferRequest and returns common.LogFields for logging.

type ClientOfferResponse

type ClientOfferResponse struct {
	Limited                      bool                     `cbor:"1,keyasint,omitempty"`
	NoMatch                      bool                     `cbor:"2,keyasint,omitempty"`
	ConnectionID                 ID                       `cbor:"3,keyasint,omitempty"`
	SelectedProxyProtocolVersion int32                    `cbor:"4,keyasint,omitempty"`
	ProxyAnswerSDP               WebRTCSessionDescription `cbor:"5,keyasint,omitempty"`
	RelayPacketToServer          []byte                   `cbor:"6,keyasint,omitempty"`
}

ClientOfferResponse returns the connecting information for a matched proxy. The proxy's WebRTC SDP is an answer to the offer sent in ClientOfferRequest and is used to begin dialing the WebRTC connection.

Once the client completes its connection to the Psiphon server, it must relay a BrokerServerReport to the server on behalf of the broker. This relay is conducted within a secure session. First, the client sends RelayPacketToServer to the server. Then the client relays any responses to the broker using ClientRelayedPacketRequests and continues to relay using ClientRelayedPacketRequests until complete. ConnectionID identifies this connection and its relayed BrokerServerReport.

func UnmarshalClientOfferResponse

func UnmarshalClientOfferResponse(payload []byte) (*ClientOfferResponse, error)

type ClientRelayedPacketRequest

type ClientRelayedPacketRequest struct {
	ConnectionID     ID     `cbor:"1,keyasint,omitempty"`
	PacketFromServer []byte `cbor:"2,keyasint,omitempty"`
}

ClientRelayedPacketRequest is an API request sent from a client to a broker, relaying a secure session packet from the Psiphon server to the broker. This relay is a continuation of the broker/server exchange begun with ClientOfferResponse.RelayPacketToServer. PacketFromServer is the next packet from the server.

When a broker attempts to use an existing session which has expired on the server, the packet from the server may contain a signed reset session token, which is used to automatically reset and start establishing a new session before relaying the payload.

func UnmarshalClientRelayedPacketRequest

func UnmarshalClientRelayedPacketRequest(payload []byte) (*ClientRelayedPacketRequest, error)

func (*ClientRelayedPacketRequest) ValidateAndGetLogFields

func (request *ClientRelayedPacketRequest) ValidateAndGetLogFields(
	baseAPIParameterValidator common.APIParameterValidator,
	formatter common.APIParameterLogFieldFormatter,
	geoIPData common.GeoIPData) (common.LogFields, error)

ValidateAndGetLogFields validates the ClientRelayedPacketRequest and returns common.LogFields for logging.

type ClientRelayedPacketResponse

type ClientRelayedPacketResponse struct {
	PacketToServer []byte `cbor:"1,keyasint,omitempty"`
}

ClientRelayedPacketResponse returns the next packet from the broker to the server. When PacketToServer is empty, the broker/server exchange is done and the client stops relaying packets.

func UnmarshalClientRelayedPacketResponse

func UnmarshalClientRelayedPacketResponse(payload []byte) (*ClientRelayedPacketResponse, error)

type DataChannelTrafficShapingParameters

type DataChannelTrafficShapingParameters struct {
	MinPaddedMessages       int     `cbor:"1,keyasint,omitempty"`
	MaxPaddedMessages       int     `cbor:"2,keyasint,omitempty"`
	MinPaddingSize          int     `cbor:"3,keyasint,omitempty"`
	MaxPaddingSize          int     `cbor:"4,keyasint,omitempty"`
	MinDecoyMessages        int     `cbor:"5,keyasint,omitempty"`
	MaxDecoyMessages        int     `cbor:"6,keyasint,omitempty"`
	MinDecoySize            int     `cbor:"7,keyasint,omitempty"`
	MaxDecoySize            int     `cbor:"8,keyasint,omitempty"`
	DecoyMessageProbability float64 `cbor:"9,keyasint,omitempty"`
}

DataChannelTrafficShapingParameters specifies a data channel traffic shaping configuration, including random padding and decoy messages. Clients determine their own traffic shaping configuration, and generate and send a configuration for the peer proxy to use.

func (*DataChannelTrafficShapingParameters) Validate

func (params *DataChannelTrafficShapingParameters) Validate() error

Validate validates the that client has not specified excess traffic shaping padding or decoy traffic.

type ExtendTransportTimeout

type ExtendTransportTimeout func(timeout time.Duration)

ExtendTransportTimeout is a callback that extends the timeout for a server-side broker transport handler, facilitating request-specific timeouts including long-polling for proxy announcements.

type GetTactics

type GetTactics func(common.GeoIPData, common.APIParameters) ([]byte, string, error)

GetTactics is a callback which returns the appropriate tactics for the specified client/proxy GeoIP data and API parameters.

type ICECandidateType

type ICECandidateType int32

ICECandidateType is an ICE candidate type: host for public addresses, port mapping for when a port mapping protocol was used to establish a public address, or server reflexive when STUN hole punching was used to create a public address. Peer reflexive candidates emerge during the ICE negotiation process and are not SDP entries.

const (
	ICECandidateUnknown ICECandidateType = iota
	ICECandidateHost
	ICECandidatePortMapping
	ICECandidateServerReflexive
	ICECandidatePeerReflexive
)

func (ICECandidateType) IsValid

func (t ICECandidateType) IsValid() bool

func (ICECandidateType) MarshalText

func (t ICECandidateType) MarshalText() ([]byte, error)

MarshalText ensures the string representation of the value is logged in JSON.

func (ICECandidateType) String

func (t ICECandidateType) String() string

type ICECandidateTypes

type ICECandidateTypes []ICECandidateType

ICECandidateTypes is a list of ICE candidate types.

func (ICECandidateTypes) IsValid

func (t ICECandidateTypes) IsValid() bool

type ID

type ID [32]byte

ID is a unique identifier used to identify inproxy connections and actors.

func IDFromString

func IDFromString(s string) (ID, error)

IDFromString returns an ID given its string encoding.

func IDsFromStrings

func IDsFromStrings(strs []string) ([]ID, error)

IDsFromStrings returns a list of IDs given a list of string encodings.

func MakeID

func MakeID() (ID, error)

MakeID generates a new ID using crypto/rand.

func (ID) Equal

func (id ID) Equal(x ID) bool

Equal indicates whether two IDs are equal. It uses a constant time comparison.

func (ID) MarshalText

func (id ID) MarshalText() ([]byte, error)

MarshalText emits IDs as base64.

func (ID) String

func (id ID) String() string

String emits IDs as base64.

func (ID) Zero

func (id ID) Zero() bool

Zero indicates whether the ID is the zero value.

type InitiatorRoundTrip

type InitiatorRoundTrip struct {
	// contains filtered or unexported fields
}

InitiatorRoundTrip represents the state of a session round trip, including a session handshake if required. The session handshake and round trip is advanced by calling InitiatorRoundTrip.Next.

func (*InitiatorRoundTrip) Next

func (r *InitiatorRoundTrip) Next(
	ctx context.Context,
	receivedPacket []byte) (retSendPacket []byte, retIsRequestPacket bool, retErr error)

Next advances a round trip, as well as any session handshake that may be first required. Next takes the next packet received from the responder and returns the next packet to send to the responder. To begin, pass a nil receivedPacket. The round trip is complete when Next returns nil for the next packet to send; the response can be fetched from InitiatorRoundTrip.Response.

When waitToShareSession is set, Next will block until an existing, non-established session is available to be shared.

Multiple concurrent round trips are supported and requests from different round trips can arrive at the responder out-of-order. The provided transport is responsible for multiplexing round trips and maintaining an association between sent and received packets for a given round trip.

Next returns immediately when ctx becomes done.

func (*InitiatorRoundTrip) Response

func (r *InitiatorRoundTrip) Response() ([]byte, error)

Response returns the round trip response. Call Response after Next returns nil for the next packet to send, indicating that the round trip is complete.

func (*InitiatorRoundTrip) TransportFailed

func (r *InitiatorRoundTrip) TransportFailed()

TransportFailed marks any owned, not yet ready-to-share session as failed and signals any other initiators waiting to share the session.

TransportFailed should be called when using waitToShareSession and when there is a transport level failure to relay a session packet.

type InitiatorSessions

type InitiatorSessions struct {
	// contains filtered or unexported fields
}

InitiatorSessions is a set of secure Noise protocol sessions for an initiator. For in-proxy, clients and proxies will initiate sessions with one more brokers and brokers will initiate sessions with multiple Psiphon servers.

Secure sessions provide encryption, authentication of the responder, identity hiding for the initiator, forward secrecy, and anti-replay for application data.

Maintaining a set of established sessions minimizes round trips and overhead, as established sessions can be shared and reused for many client requests to one broker or many broker requests to one server.

Currently, InitiatorSessions doesn't not cap the number of sessions or use an LRU cache since the number of peers is bounded in the in-proxy architecture; clients will typically use one or no more than a handful of brokers and brokers will exchange requests with a subset of Psiphon servers bounded by the in-proxy capability.

InitiatorSessions are used via the RoundTrip function or InitiatorRoundTrip type. RoundTrip is a synchronous function which performs any necessary session establishment handshake along with the request/response exchange. InitiatorRoundTrip offers an iterator interface, with stepwise invocations for each step of the handshake and round trip.

All round trips attempt to share and reuse any existing, established session to a given peer. For a given peer, the waitToShareSession option determines whether round trips will block and wait if a session handshake is already in progress, or proceed with a concurrent handshake. For in-proxy, clients and proxies use waitToShareSession; as broker/server round trips are relayed through clients, brokers do not use waitToShareSession so as to not rely on any single client.

Round trips can be performed concurrently and requests can arrive out-of- order. The higher level transport for sessions is responsible for multiplexing round trips and maintaining the association between a request and it's corresponding response.

func NewInitiatorSessions

func NewInitiatorSessions(
	initiatorPrivateKey SessionPrivateKey) *InitiatorSessions

NewInitiatorSessions creates a new InitiatorSessions with the specified initator private key.

func (*InitiatorSessions) NewRoundTrip

func (s *InitiatorSessions) NewRoundTrip(
	responderPublicKey SessionPublicKey,
	responderRootObfuscationSecret ObfuscationSecret,
	waitToShareSession bool,
	request []byte) (*InitiatorRoundTrip, error)

NewRoundTrip creates a new InitiatorRoundTrip which will perform a request/response round trip with the specified responder, sending the input request. The InitiatorRoundTrip will establish a session when required, or reuse an existing session when available.

When waitToShareSession is true, InitiatorRoundTrip.Next will block until an existing, non-established session is available to be shared.

Limitation with waitToShareSession: currently, any new session must complete an _application-level_ round trip (e.g., ProxyAnnounce/ClientOffer request _and_ response) before the session becomes ready to share since the first application-level request is sent in the same packet as the last handshake message and ready-to-share is only signalled after a subsequent packet is received. This means that, for example, a long-polling ProxyAnnounce will block any additional ProxyAnnounce requests attempting to share the same InitiatorSessions. In practice, an initial ProxyAnnounce/ClientOffer request is expected to block only as long as there is no match, so the impact of blocking other concurrent requests is limited. See comment in InitiatorRoundTrip.Next for a related future enhancement.

NewRoundTrip does not block or perform any session operations; the operations begin on the first InitiatorRoundTrip.Next call. The content of request should not be modified after calling NewRoundTrip.

func (*InitiatorSessions) RoundTrip

func (s *InitiatorSessions) RoundTrip(
	ctx context.Context,
	roundTripper RoundTripper,
	responderPublicKey SessionPublicKey,
	responderRootObfuscationSecret ObfuscationSecret,
	waitToShareSession bool,
	sessionHandshakeTimeout time.Duration,
	requestDelay time.Duration,
	requestTimeout time.Duration,
	request []byte) ([]byte, error)

RoundTrip sends the request to the specified responder and returns the response.

RoundTrip will establish a session when required, or reuse an existing session when available.

When waitToShareSession is true, RoundTrip will block until an existing, non-established session is available to be shared.

When making initial network round trips to establish a session, sessionHandshakeTimeout is applied as the round trip timeout.

When making the application-level request round trip, requestDelay, when > 0, is applied before the request network round trip begins; requestDelay may be used to spread out many concurrent requests, such as batch proxy announcements, to avoid CDN rate limits.

requestTimeout is applied to the application-level request network round trip, and excludes any requestDelay; the distinct requestTimeout may be used to set a longer timeout for long-polling requests, such as proxy announcements.

Any time spent blocking on waitToShareSession is not included in requestDelay or requestTimeout.

RoundTrip returns immediately when ctx becomes done.

type LookupGeoIP

type LookupGeoIP func(IP string) common.GeoIPData

LookupGeoIP is a callback for providing GeoIP lookup service.

type MatchAnnouncement

type MatchAnnouncement struct {
	Properties           MatchProperties
	ProxyID              ID
	ConnectionID         ID
	ProxyProtocolVersion int32
}

MatchAnnouncement is a proxy announcement to be queued for matching.

type MatchAnswer

type MatchAnswer struct {
	ProxyIP                      string
	ProxyID                      ID
	ConnectionID                 ID
	SelectedProxyProtocolVersion int32
	ProxyAnswerSDP               WebRTCSessionDescription
}

MatchAnswer is a proxy answer, the proxy's follow up to a matched announcement, to be routed to the awaiting client offer.

type MatchMetrics

type MatchMetrics struct {
	OfferMatchIndex        int
	OfferQueueSize         int
	AnnouncementMatchIndex int
	AnnouncementQueueSize  int
}

MatchMetrics records statistics about the match queue state at the time a match is made.

func (*MatchMetrics) GetMetrics

func (metrics *MatchMetrics) GetMetrics() common.LogFields

GetMetrics converts MatchMetrics to loggable fields.

type MatchOffer

type MatchOffer struct {
	Properties                  MatchProperties
	ClientProxyProtocolVersion  int32
	ClientOfferSDP              WebRTCSessionDescription
	ClientRootObfuscationSecret ObfuscationSecret
	DoDTLSRandomization         bool
	TrafficShapingParameters    *DataChannelTrafficShapingParameters
	NetworkProtocol             NetworkProtocol
	DestinationAddress          string
	DestinationServerID         string
}

MatchOffer is a client offer to be queued for matching.

type MatchProperties

type MatchProperties struct {
	CommonCompartmentIDs   []ID
	PersonalCompartmentIDs []ID
	GeoIPData              common.GeoIPData
	NetworkType            NetworkType
	NATType                NATType
	PortMappingTypes       PortMappingTypes
}

MatchProperties specifies the compartment, GeoIP, and network topology matching roperties of clients and proxies.

func (*MatchProperties) EffectiveNATType

func (p *MatchProperties) EffectiveNATType() NATType

EffectiveNATType combines the set of network properties into an effective NAT type. When a port mapping is offered, a NAT type with unlimiter NAT traversal is assumed. When NAT type is unknown and the network type is mobile, CGNAT with limited NAT traversal is assumed.

func (*MatchProperties) ExistsPreferredNATMatch

func (p *MatchProperties) ExistsPreferredNATMatch(
	unlimitedNAT, partiallyLimitedNAT, limitedNAT bool) bool

ExistsPreferredNATMatch indicates whether there exists a preferred NAT matching given the types of pairing candidates available.

func (*MatchProperties) IsPersonalCompartmentalized

func (p *MatchProperties) IsPersonalCompartmentalized() bool

IsPersonalCompartmentalized indicates whether the candidate has personal compartment IDs.

func (*MatchProperties) IsPreferredNATMatch

func (p *MatchProperties) IsPreferredNATMatch(
	peerMatchProperties *MatchProperties) bool

IsPreferredNATMatch indicates whether the peer candidate is a preferred NAT matching.

type Matcher

type Matcher struct {
	// contains filtered or unexported fields
}

Matcher matches proxy announcements with client offers. Matcher also coordinates pending proxy answers and routes answers to the awaiting client offer handler.

Matching prioritizes selecting the oldest announcements and client offers, as they are closest to timing out.

The client and proxy must supply matching personal or common compartment IDs. Personal compartment matching is preferred. Common compartments are managed by Psiphon and can be obtained via a tactics parameter or via an OSL embedding.

A client may opt form personal-only matching by not supplying any common compartment IDs.

Matching prefers to pair proxies and clients in a way that maximizes total possible matches. For a client or proxy with less-limited NAT traversal, a pairing with more-limited NAT traversal is preferred; and vice versa. Candidates with unknown NAT types and mobile network types are assumed to have the most limited NAT traversal capability.

Preferred matchings take priority over announcement age.

The client and proxy will not match if they are in the same country and ASN, as it's assumed that doesn't provide any blocking circumvention benefit. Disallowing proxies in certain blocked countries is handled at a higher level; any such proxies should not be enqueued for matching.

func NewMatcher

func NewMatcher(config *MatcherConfig) *Matcher

NewMatcher creates a new Matcher.

func (*Matcher) Announce

func (m *Matcher) Announce(
	ctx context.Context,
	proxyIP string,
	proxyAnnouncement *MatchAnnouncement) (*MatchOffer, *MatchMetrics, error)

Announce enqueues the proxy announcement and blocks until it is matched with a returned offer or ctx is done. The caller must not mutate the announcement or its properties after calling Announce.

The offer is sent to the proxy by the broker, and then the proxy sends its answer back to the broker, which calls Answer with that value.

The returned MatchMetrics is nil unless a match is made; and non-nil if a match is made, even if there is a later error.

func (*Matcher) Answer

func (m *Matcher) Answer(
	proxyAnswer *MatchAnswer) error

Answer delivers an answer from the proxy for a previously matched offer. The ProxyID and ConnectionID must correspond to the original announcement. The caller must not mutate the answer after calling Answer. Answer does not block.

The answer is returned to the awaiting Offer call and sent to the matched client.

func (*Matcher) AnswerError

func (m *Matcher) AnswerError(proxyID ID, connectionID ID)

AnswerError delivers a failed answer indication from the proxy to an awaiting offer. The ProxyID and ConnectionID must correspond to the original announcement.

The failure indication is returned to the awaiting Offer call and sent to the matched client.

func (*Matcher) Offer

func (m *Matcher) Offer(
	ctx context.Context,
	clientIP string,
	clientOffer *MatchOffer) (*MatchAnswer, *MatchAnnouncement, *MatchMetrics, error)

Offer enqueues the client offer and blocks until it is matched with a returned announcement or ctx is done. The caller must not mutate the offer or its properties after calling Announce.

The answer is returned to the client by the broker, and the WebRTC connection is dialed. The original announcement is also returned, so its match properties can be logged.

The returned MatchMetrics is nil unless a match is made; and non-nil if a match is made, even if there is a later error.

func (*Matcher) SetLimits

func (m *Matcher) SetLimits(
	announcementLimitEntryCount int,
	announcementRateLimitQuantity int,
	announcementRateLimitInterval time.Duration,
	announcementNonlimitedProxyIDs []ID,
	offerLimitEntryCount int,
	offerRateLimitQuantity int,
	offerRateLimitInterval time.Duration)

SetLimits sets new queue limits, replacing the previous configuration. Existing, cached rate limiters retain their existing rate limit state. New entries will use the new quantity/interval configuration. In addition, currently enqueued items may exceed any new, lower maximum entry count until naturally dequeued.

func (*Matcher) Start

func (m *Matcher) Start() error

Start starts running the Matcher. The Matcher runs a goroutine which matches announcements and offers.

func (*Matcher) Stop

func (m *Matcher) Stop()

Stop stops running the Matcher and its worker goroutine.

Limitation: Stop is not synchronized with Announce/Offer/Answer, so items can get enqueued during and after a Stop call. Stop is intended more for a full broker shutdown, where this won't be a concern.

type MatcherConfig

type MatcherConfig struct {

	// Logger is used to log events.
	Logger common.Logger

	// Accouncement queue limits.
	AnnouncementLimitEntryCount    int
	AnnouncementRateLimitQuantity  int
	AnnouncementRateLimitInterval  time.Duration
	AnnouncementNonlimitedProxyIDs []ID

	// Offer queue limits.
	OfferLimitEntryCount   int
	OfferRateLimitQuantity int
	OfferRateLimitInterval time.Duration
}

MatcherConfig specifies the configuration for a matcher.

type MatcherLimitError

type MatcherLimitError struct {
	// contains filtered or unexported fields
}

MatcherLimitError is the error type returned by Announce or Offer when the caller has exceeded configured queue entry or rate limits.

func NewMatcherLimitError

func NewMatcherLimitError(err error) *MatcherLimitError

func (MatcherLimitError) Error

func (e MatcherLimitError) Error() string

type NATDiscoverConfig

type NATDiscoverConfig struct {

	// Logger is used to log events.
	Logger common.Logger

	// WebRTCDialCoordinator specifies specific STUN and discovery and
	// settings, and receives discovery results.
	WebRTCDialCoordinator WebRTCDialCoordinator

	// SkipPortMapping indicates whether to skip port mapping type discovery,
	// as clients do since they will gather the same stats during the WebRTC
	// offer preparation.
	SkipPortMapping bool
}

NATDiscoverConfig specifies the configuration for a NATDiscover run.

type NATFiltering

type NATFiltering int32

NATMapping is a NAT filtering behavior defined in RFC 4787, section 5.

const (
	NATFilteringUnknown NATFiltering = iota
	NATFilteringEndpointIndependent
	NATFilteringAddressDependent
	NATFilteringAddressPortDependent
)

func (NATFiltering) IsValid

func (f NATFiltering) IsValid() bool

func (NATFiltering) MarshalText

func (f NATFiltering) MarshalText() ([]byte, error)

MarshalText ensures the string representation of the value is logged in JSON.

func (NATFiltering) String

func (f NATFiltering) String() string

type NATMapping

type NATMapping int32

NATMapping is a NAT mapping behavior defined in RFC 4787, section 4.1.

const (
	NATMappingUnknown NATMapping = iota
	NATMappingEndpointIndependent
	NATMappingAddressDependent
	NATMappingAddressPortDependent
)

func (NATMapping) IsValid

func (m NATMapping) IsValid() bool

func (NATMapping) MarshalText

func (m NATMapping) MarshalText() ([]byte, error)

MarshalText ensures the string representation of the value is logged in JSON.

func (NATMapping) String

func (m NATMapping) String() string

type NATTraversal

type NATTraversal int32

NATTraversal classifies the NAT traversal potential for a NATType. NATTypes are determined to be compatible -- that is, a connection between the corresponding networks can be established via STUN hole punching -- based on their respective NATTraversal classifications.

const (
	NATTraversalUnlimited NATTraversal = iota
	NATTraversalPartiallyLimited
	NATTraversalStrictlyLimited
)

func MakeTraversal

func MakeTraversal(t NATType) NATTraversal

MakeTraversal returns the NATTraversal classification for the given NATType.

func (NATTraversal) Compatible

func (t NATTraversal) Compatible(t1 NATTraversal) bool

Compatible indicates whether the NATTraversals are compatible.

func (NATTraversal) ExistsPreferredMatch

func (t NATTraversal) ExistsPreferredMatch(unlimited, partiallyLimited, strictlyLimited bool) bool

ExistsPreferredMatch indicates whether a preferred match exists, for this NATTraversal, when there are unlimited/partiallyLimited/strictlyLimited candidates available.

func (NATTraversal) IsPreferredMatch

func (t NATTraversal) IsPreferredMatch(t1 NATTraversal) bool

IsPreferredMatch indicates whether the peer NATTraversal is a preferred match for this NATTraversal. A match is preferred, and so prioritized, when one of the two NATTraversals is more limited, but the pair is still compatible. This preference attempt to reserve less limited match candidates for those peers that need them.

type NATType

type NATType int32

NATType specifies a network's NAT behavior and consists of a NATMapping and a NATFiltering component.

func MakeNATType

func MakeNATType(mapping NATMapping, filtering NATFiltering) NATType

MakeNATType creates a new NATType.

func (NATType) Compatible

func (t NATType) Compatible(t1 NATType) bool

Compatible indicates whether the NATType NATTraversals are compatible.

func (NATType) ExistsPreferredMatch

func (t NATType) ExistsPreferredMatch(unlimited, partiallyLimited, limited bool) bool

ExistsPreferredMatch indicates whhether there exists a preferred match for the NATType's NATTraversal.

func (NATType) Filtering

func (t NATType) Filtering() NATFiltering

Filtering extracts the NATFiltering component of this NATType.

func (NATType) IsPreferredMatch

func (t NATType) IsPreferredMatch(t1 NATType) bool

IsPreferredMatch indicates whether the peer NATType's NATTraversal is preferred.

func (NATType) IsValid

func (t NATType) IsValid() bool

func (NATType) Mapping

func (t NATType) Mapping() NATMapping

Mapping extracts the NATMapping component of this NATType.

func (NATType) MarshalText

func (t NATType) MarshalText() ([]byte, error)

MarshalText ensures the string representation of the value is logged in JSON.

func (NATType) NeedsDiscovery

func (t NATType) NeedsDiscovery() bool

NeedsDiscovery indicates that the NATType is unknown and should be discovered.

func (NATType) String

func (t NATType) String() string

func (NATType) Traversal

func (t NATType) Traversal() NATTraversal

Traversal returns the NATTraversal classification for this NATType.

type NetworkProtocol

type NetworkProtocol int32

NetworkProtocol is an Internet protocol, such as TCP or UDP. This enum is used for compact API message encoding.

const (
	NetworkProtocolTCP NetworkProtocol = iota
	NetworkProtocolUDP
)

func NetworkProtocolFromString

func NetworkProtocolFromString(networkProtocol string) (NetworkProtocol, error)

NetworkProtocolFromString converts a "net" package network protocol string value to a NetworkProtocol.

func (NetworkProtocol) IsStream

func (p NetworkProtocol) IsStream() bool

IsStream indicates if the NetworkProtocol is stream-oriented (e.g., TCP) and not packet-oriented (e.g., UDP).

func (NetworkProtocol) String

func (p NetworkProtocol) String() string

String converts a NetworkProtocol to a "net" package network protocol string.

type NetworkType

type NetworkType int32

NetworkType is the type of a network, such as WiFi or Mobile. This enum is used for compact API message encoding.

const (
	NetworkTypeUnknown NetworkType = iota
	NetworkTypeWiFi
	NetworkTypeMobile
)

func GetNetworkType

func GetNetworkType(packedBaseParams protocol.PackedAPIParameters) NetworkType

GetNetworkType extracts the network_type from base API metrics and returns a corresponding NetworkType. This is the one base metric that is used in the broker logic, and not simply logged.

type ObfuscationSecret

type ObfuscationSecret [32]byte

ObfuscationSecret is shared, semisecret value used in obfuscation layers.

func GenerateRootObfuscationSecret

func GenerateRootObfuscationSecret() (ObfuscationSecret, error)

GenerateRootObfuscationSecret creates a new ObfuscationSecret using crypto/rand.

func ObfuscationSecretFromString

func ObfuscationSecretFromString(s string) (ObfuscationSecret, error)

ObfuscationSecretFromString returns an ObfuscationSecret given its string encoding.

func (ObfuscationSecret) String

func (secret ObfuscationSecret) String() string

String emits ObfuscationSecrets as base64.

type PortMappingType

type PortMappingType int32

PortMappingType is a port mapping protocol supported by a network. Values include UPnP-IGD, NAT-PMP, and PCP.

const (
	PortMappingTypeNone PortMappingType = iota
	PortMappingTypeUPnP
	PortMappingTypePMP
	PortMappingTypePCP
)

func (PortMappingType) IsValid

func (t PortMappingType) IsValid() bool

func (PortMappingType) MarshalText

func (t PortMappingType) MarshalText() ([]byte, error)

MarshalText ensures the string representation of the value is logged in JSON.

func (PortMappingType) String

func (t PortMappingType) String() string

type PortMappingTypes

type PortMappingTypes []PortMappingType

PortMappingTypes is a list of port mapping protocol supported by a network.

func (PortMappingTypes) Available

func (t PortMappingTypes) Available() bool

Available indicates that at least one port mapping protocol is supported.

func (PortMappingTypes) IsValid

func (t PortMappingTypes) IsValid() bool

func (PortMappingTypes) NeedsDiscovery

func (t PortMappingTypes) NeedsDiscovery() bool

NeedsDiscovery indicates that the list of port mapping types is empty and should be discovered. If a network has no supported port mapping types, its list will include PortMappingTypeNone.

type ProxiedConnectionHandler

type ProxiedConnectionHandler func(
	brokerVerifiedOriginalClientIP string,
	logFields common.LogFields)

ProxiedConnectionHandler is a callback, provided by the Psiphon server, that receives information from a BrokerServerReport for the client associated with the callback.

The server must use the brokerVerifiedOriginalClientIP for all GeoIP operations associated with the client, including traffic rule selection and client-side tactics selection.

Since the BrokerServerReport may be delivered later than the Psiphon handshake request -- in the case where the broker/server session needs to be established there will be additional round trips -- the server should delay traffic rule application, tactics responses, and allowing tunneled traffic until after the ProxiedConnectionHandler callback is invoked for the client. As a consequence, Psiphon Servers should be configured to require Proxies to be used for designated protocols. It's expected that server-side tactics such as packet manipulation will be applied based on the proxy's IP address.

The fields in logFields should be added to server_tunnel logs.

type Proxy

type Proxy struct {
	// contains filtered or unexported fields
}

Proxy is the in-proxy proxying component, which relays traffic from a client to a Psiphon server.

func NewProxy

func NewProxy(config *ProxyConfig) (*Proxy, error)

NewProxy initializes a new Proxy with the specified configuration.

func (*Proxy) Run

func (p *Proxy) Run(ctx context.Context)

Run runs the proxy. The proxy sends requests to the Broker announcing its availability; the Broker matches the proxy with clients, and facilitates an exchange of WebRTC connection information; the proxy and each client attempt to establish a connection; and the client's traffic is relayed to Psiphon server.

Run ends when ctx is Done. A proxy run may continue across underlying network changes assuming that the ProxyConfig GetBrokerClient and MakeWebRTCDialCoordinator callbacks react to network changes and provide instances that are reflect network changes.

type ProxyAnnounceRequest

type ProxyAnnounceRequest struct {
	PersonalCompartmentIDs []ID          `cbor:"1,keyasint,omitempty"`
	Metrics                *ProxyMetrics `cbor:"2,keyasint,omitempty"`
}

ProxyAnnounceRequest is an API request sent from a proxy to a broker, announcing that it is available for a client connection. Proxies send one ProxyAnnounceRequest for each available client connection. The broker will match the proxy with a a client and return WebRTC connection information in the response.

PersonalCompartmentIDs limits the clients to those that supply one of the specified compartment IDs; personal compartment IDs are distributed from proxy operators to client users out-of-band and provide optional access control.

The proxy's session public key is an implicit and cryptographically verified proxy ID.

func UnmarshalProxyAnnounceRequest

func UnmarshalProxyAnnounceRequest(payload []byte) (*ProxyAnnounceRequest, error)

func (*ProxyAnnounceRequest) ValidateAndGetParametersAndLogFields

func (request *ProxyAnnounceRequest) ValidateAndGetParametersAndLogFields(
	maxCompartmentIDs int,
	baseAPIParameterValidator common.APIParameterValidator,
	formatter common.APIParameterLogFieldFormatter,
	geoIPData common.GeoIPData) (common.APIParameters, common.LogFields, error)

ValidateAndGetParametersAndLogFields validates the ProxyAnnounceRequest and returns Psiphon API parameters for processing and common.LogFields for logging.

type ProxyAnnounceResponse

type ProxyAnnounceResponse struct {
	OperatorMessageJSON         string                               `cbor:"1,keyasint,omitempty"`
	TacticsPayload              []byte                               `cbor:"2,keyasint,omitempty"`
	Limited                     bool                                 `cbor:"3,keyasint,omitempty"`
	NoMatch                     bool                                 `cbor:"4,keyasint,omitempty"`
	ConnectionID                ID                                   `cbor:"5,keyasint,omitempty"`
	ClientProxyProtocolVersion  int32                                `cbor:"6,keyasint,omitempty"`
	ClientOfferSDP              WebRTCSessionDescription             `cbor:"7,keyasint,omitempty"`
	ClientRootObfuscationSecret ObfuscationSecret                    `cbor:"8,keyasint,omitempty"`
	DoDTLSRandomization         bool                                 `cbor:"9,keyasint,omitempty"`
	TrafficShapingParameters    *DataChannelTrafficShapingParameters `cbor:"10,keyasint,omitempty"`
	NetworkProtocol             NetworkProtocol                      `cbor:"11,keyasint,omitempty"`
	DestinationAddress          string                               `cbor:"12,keyasint,omitempty"`
}

ProxyAnnounceResponse returns the connection information for a matched client. To establish a WebRTC connection, the proxy uses the client's offer SDP to create its own answer SDP and send that to the broker in a subsequent ProxyAnswerRequest. The ConnectionID is a unique identifier for this single connection and must be relayed back in the ProxyAnswerRequest.

ClientRootObfuscationSecret is generated (or replayed) by the client and sent to the proxy and used to drive obfuscation operations.

DestinationAddress is the dial address for the Psiphon server the proxy is to relay client traffic with. The broker validates that the dial address corresponds to a valid Psiphon server.

OperatorMessageJSON is an optional message bundle to be forwarded to the user interface for display to the user; for example, to alert the proxy operator of configuration issue; the JSON schema is not defined here.

func UnmarshalProxyAnnounceResponse

func UnmarshalProxyAnnounceResponse(payload []byte) (*ProxyAnnounceResponse, error)

type ProxyAnswerRequest

type ProxyAnswerRequest struct {
	ConnectionID                 ID                       `cbor:"1,keyasint,omitempty"`
	SelectedProxyProtocolVersion int32                    `cbor:"2,keyasint,omitempty"`
	ProxyAnswerSDP               WebRTCSessionDescription `cbor:"3,keyasint,omitempty"`
	ICECandidateTypes            ICECandidateTypes        `cbor:"4,keyasint,omitempty"`
	AnswerError                  string                   `cbor:"5,keyasint,omitempty"`
}

ProxyAnswerRequest is an API request sent from a proxy to a broker, following ProxyAnnounceResponse, with the WebRTC answer SDP corresponding to the client offer SDP received in ProxyAnnounceResponse. ConnectionID identifies the connection begun in ProxyAnnounceResponse.

If the proxy was unable to establish an answer SDP or failed for some other reason, it should still send ProxyAnswerRequest with AnswerError populated; the broker will signal the client to abort this connection.

func UnmarshalProxyAnswerRequest

func UnmarshalProxyAnswerRequest(payload []byte) (*ProxyAnswerRequest, error)

func (*ProxyAnswerRequest) ValidateAndGetLogFields

func (request *ProxyAnswerRequest) ValidateAndGetLogFields(
	lookupGeoIP LookupGeoIP,
	baseAPIParameterValidator common.APIParameterValidator,
	formatter common.APIParameterLogFieldFormatter,
	geoIPData common.GeoIPData) (common.LogFields, error)

ValidateAndGetLogFields validates the ProxyAnswerRequest and returns common.LogFields for logging.

type ProxyAnswerResponse

type ProxyAnswerResponse struct {
}

ProxyAnswerResponse is the acknowledgement for a ProxyAnswerRequest.

func UnmarshalProxyAnswerResponse

func UnmarshalProxyAnswerResponse(payload []byte) (*ProxyAnswerResponse, error)

type ProxyConfig

type ProxyConfig struct {

	// Logger is used to log events.
	Logger common.Logger

	// EnableWebRTCDebugLogging indicates whether to emit WebRTC debug logs.
	EnableWebRTCDebugLogging bool

	// WaitForNetworkConnectivity is a callback that should block until there
	// is network connectivity or shutdown. The return value is true when
	// there is network connectivity, and false for shutdown.
	WaitForNetworkConnectivity func() bool

	// GetBrokerClient provides a BrokerClient which the proxy will use for
	// making broker requests. If GetBrokerClient returns a shared
	// BrokerClient instance, the BrokerClient must support multiple,
	// concurrent round trips, as the proxy will use it to concurrently
	// announce many proxy instances. The BrokerClient should be implemented
	// using multiplexing over a shared network connection -- for example,
	// HTTP/2 --  and a shared broker session for optimal performance.
	GetBrokerClient func() (*BrokerClient, error)

	// GetBaseAPIParameters returns Psiphon API parameters to be sent to and
	// logged by the broker. Expected parameters include client/proxy
	// application and build version information. GetBaseAPIParameters also
	// returns the network ID, corresponding to the parameters, to be used in
	// tactics logic; the network ID is not sent to the broker.
	GetBaseAPIParameters func() (common.APIParameters, string, error)

	// MakeWebRTCDialCoordinator provides a WebRTCDialCoordinator which
	// specifies WebRTC-related dial parameters, including selected STUN
	// server addresses; network topology information for the current netork;
	// NAT logic settings; and other settings.
	//
	// MakeWebRTCDialCoordinator is invoked for each proxy/client connection,
	// and the provider can select new parameters per connection as reqired.
	MakeWebRTCDialCoordinator func() (WebRTCDialCoordinator, error)

	// HandleTacticsPayload is a callback that receives any tactics payload,
	// provided by the broker in proxy announcement request responses.
	// HandleTacticsPayload must return true when the tacticsPayload includes
	// new tactics, indicating that the proxy should reinitialize components
	// controlled by tactics parameters.
	HandleTacticsPayload func(networkID string, tacticsPayload []byte) bool

	// OperatorMessageHandler is a callback that is invoked with any user
	// message JSON object that is sent to the Proxy from the Broker. This
	// facility may be used to alert proxy operators when required. The JSON
	// object schema is arbitrary and not defined here.
	OperatorMessageHandler func(messageJSON string)

	// MaxClients is the maximum number of clients that are allowed to connect
	// to the proxy.
	MaxClients int

	// LimitUpstreamBytesPerSecond limits the upstream data transfer rate for
	// a single client. When 0, there is no limit.
	LimitUpstreamBytesPerSecond int

	// LimitDownstreamBytesPerSecond limits the downstream data transfer rate
	// for a single client. When 0, there is no limit.
	LimitDownstreamBytesPerSecond int

	// ActivityUpdater specifies an ActivityUpdater for activity associated
	// with this proxy.
	ActivityUpdater ActivityUpdater
}

ProxyConfig specifies the configuration for a Proxy run.

type ProxyMetrics

type ProxyMetrics struct {
	BaseAPIParameters             protocol.PackedAPIParameters `cbor:"1,keyasint,omitempty"`
	ProxyProtocolVersion          int32                        `cbor:"2,keyasint,omitempty"`
	NATType                       NATType                      `cbor:"3,keyasint,omitempty"`
	PortMappingTypes              PortMappingTypes             `cbor:"4,keyasint,omitempty"`
	MaxClients                    int32                        `cbor:"6,keyasint,omitempty"`
	ConnectingClients             int32                        `cbor:"7,keyasint,omitempty"`
	ConnectedClients              int32                        `cbor:"8,keyasint,omitempty"`
	LimitUpstreamBytesPerSecond   int64                        `cbor:"9,keyasint,omitempty"`
	LimitDownstreamBytesPerSecond int64                        `cbor:"10,keyasint,omitempty"`
	PeakUpstreamBytesPerSecond    int64                        `cbor:"11,keyasint,omitempty"`
	PeakDownstreamBytesPerSecond  int64                        `cbor:"12,keyasint,omitempty"`
}

ProxyMetrics are network topolology and resource metrics provided by a proxy to a broker. The broker uses this information when matching proxies and clients.

func (*ProxyMetrics) ValidateAndGetParametersAndLogFields

func (metrics *ProxyMetrics) ValidateAndGetParametersAndLogFields(
	baseAPIParameterValidator common.APIParameterValidator,
	formatter common.APIParameterLogFieldFormatter,
	geoIPData common.GeoIPData) (common.APIParameters, common.LogFields, error)

ValidateAndGetParametersAndLogFields validates the ProxyMetrics and returns Psiphon API parameters for processing and common.LogFields for logging.

type RequestHandler

type RequestHandler func(initiatorID ID, request []byte) ([]byte, error)

RequestHandler is an application-level handler that receives the decrypted request payload and returns a response payload to be encrypted and sent to the initiator. The initiatorID is the authenticated identifier of the initiator: client, proxy, or broker.

In cases where a request is a one-way message, with no response, such as a BrokerServerReport, RequestHandler should return a nil packet.

type ResponderSessions

type ResponderSessions struct {
	// contains filtered or unexported fields
}

ResponderSessions is a set of secure Noise protocol sessions for an responder. For in-proxy, brokers respond to clients and proxies and servers respond to brokers.

Secure sessions provide encryption, authentication of the responder, identity hiding for the initiator, forward secrecy, and anti-replay for application data.

ResponderSessions maintains a cache of established sessions to minimizes round trips and overhead as initiators are expected to make multiple round trips. The cache has a TTL and maximum size with LRU to cap overall memory usage. A broker may receive requests from millions of clients and proxies and so only more recent sessions will be retained. Servers will receive requests from only a handful of brokers, and so the TTL is not applied.

Multiple, concurrent sessions for a single initiator public key are supported.

func NewResponderSessions

func NewResponderSessions(
	responderPrivateKey SessionPrivateKey,
	responderRootObfuscationSecret ObfuscationSecret) (*ResponderSessions, error)

NewResponderSessions creates a new ResponderSessions which allows any initiators to establish a session. A TTL is applied to cached sessions.

func NewResponderSessionsForKnownInitiators

func NewResponderSessionsForKnownInitiators(
	responderPrivateKey SessionPrivateKey,
	responderRootObfuscationKey ObfuscationSecret,
	initiatorPublicKeys []SessionPublicKey) (*ResponderSessions, error)

NewResponderSessionsForKnownInitiators creates a new ResponderSessions which allows only allow-listed initiators to establish a session. No TTL is applied to cached sessions.

The NewResponderSessionsForKnownInitiators configuration is for Psiphon servers responding to brokers. Only a handful of brokers are expected to be deployed. A relatively small allow list of expected broker public keys is easy to manage, deploy, and update. No TTL is applied to keep the sessions established as much as possible and avoid extra client-relayed round trips for BrokerServerRequests.

func (*ResponderSessions) HandlePacket

func (s *ResponderSessions) HandlePacket(
	inPacket []byte,
	requestHandler RequestHandler) (retOutPacket []byte, retErr error)

HandlePacket takes a session packet, as received at the transport level, and handles session handshake and request decryption. While a session handshakes, HandlePacket returns the next handshake message to be relayed back to the initiator over the transport.

Once a session is fully established and a request is decrypted, the inner request payload is passed to the RequestHandler for application-level processing. The response received from the RequestHandler will be encrypted with the session and returned from HandlePacket as the next packet to send back over the transport. If there is no response to be returned, HandlePacket returns a nil packet.

The session packet contains a session ID that is used to route packets from many initiators to the correct session state.

Above the Noise protocol security layer, session packets have an obfuscation layer. If a packet doesn't authenticate with the expected obfuscation secret, or if a packet is replayed, HandlePacket returns an error. The obfuscation anti-replay layer covers replays of Noise handshake messages which aren't covered by the Noise nonce anti-replay. When HandlePacket returns an error, the caller should invoke anti-probing behavior, such as returning a generic 404 error from an HTTP server for HTTPS transports.

There is one expected error case with legitimate initiators: when an initiator reuses a session that is expired or no longer in the responder cache. In this case HandlePacket will return a reset session token in outPacket along with an error, and the caller should log the error and also send the packet to the initiator.

The HandlePacket caller should implement initiator rate limiting in its transport level.

func (*ResponderSessions) SetKnownInitiatorPublicKeys

func (s *ResponderSessions) SetKnownInitiatorPublicKeys(
	initiatorPublicKeys []SessionPublicKey) error

SetKnownInitiatorPublicKeys updates the set of initiator public keys which are allowed to establish sessions with the responder. Any existing sessions with keys not in the new list are deleted. Existing sessions with keys which remain in the list are retained.

type RoundTripper

type RoundTripper interface {
	RoundTrip(
		ctx context.Context,
		roundTripDelay time.Duration,
		roundTripTimeout time.Duration,
		requestPayload []byte) (responsePayload []byte, err error)
}

RoundTripper provides a request/response round trip network transport with blocking circumvention capabilities. A typical implementation is domain fronted HTTPS. RoundTripper is used by clients and proxies to make requests to brokers.

The round trip implementation must apply any specified delay before the network round trip begins; and apply the specified timeout to the network round trip, excluding any delay.

type RoundTripperFailedError

type RoundTripperFailedError struct {
	// contains filtered or unexported fields
}

RoundTripperFailedError is an error type that should be returned from RoundTripper.RoundTrip when the round trip transport has permanently failed. When RoundTrip returns an error of type RoundTripperFailedError to a broker client, the broker client will invoke BrokerClientRoundTripperFailed.

func NewRoundTripperFailedError

func NewRoundTripperFailedError(err error) *RoundTripperFailedError

func (RoundTripperFailedError) Error

func (e RoundTripperFailedError) Error() string

type ServerBrokerSessions

type ServerBrokerSessions struct {
	// contains filtered or unexported fields
}

ServerBrokerSessions manages the secure sessions that handle BrokerServerReports from brokers. Each in-proxy-capable Psiphon server maintains a ServerBrokerSessions, with a set of established sessions for each broker. Session messages are relayed between the broker and the server by the client.

func NewServerBrokerSessions

func NewServerBrokerSessions(
	serverPrivateKey SessionPrivateKey,
	serverRootObfuscationSecret ObfuscationSecret,
	brokerPublicKeys []SessionPublicKey) (*ServerBrokerSessions, error)

NewServerBrokerSessions create a new ServerBrokerSessions, with the specified key material. The expected brokers are authenticated with brokerPublicKeys, an allow list.

func (*ServerBrokerSessions) HandlePacket

func (s *ServerBrokerSessions) HandlePacket(
	logger common.Logger,
	in []byte,
	clientConnectionID ID,
	handler ProxiedConnectionHandler) (retOut []byte, retErr error)

HandlePacket handles a broker/server session packet, which are relayed by clients. In Psiphon, the packets may be exchanged in the Psiphon handshake, or in subsequent SSH requests and responses. When the broker/server session is already established, it's expected that the BrokerServerReport arrives in the packet that accompanies the Psiphon handshake, and so no additional round trip is required.

Once the session is established and a verified BrokerServerReport arrives, the information from that report is sent to the ProxiedConnectionHandler callback. The callback should be associated with the client that is relaying the packets.

clientConnectionID is the in-proxy connection ID specified by the client in its Psiphon handshake.

When the retOut return value is not nil, it should be relayed back to the client in the handshake response or other tunneled response. When retOut is nil, the relay is complete.

In the session reset token case, HandlePacket will return a non-nil retOut along with a retErr; the server should both log retErr and also relay the packet to the broker.

func (*ServerBrokerSessions) SetKnownBrokerPublicKeys

func (s *ServerBrokerSessions) SetKnownBrokerPublicKeys(
	brokerPublicKeys []SessionPublicKey) error

SetKnownBrokerPublicKeys updates the set of broker public keys which are allowed to establish sessions with the server. Any existing sessions with keys not in the new list are deleted. Existing sessions with keys which remain in the list are retained.

type SessionPacket

type SessionPacket struct {
	SessionID         ID     `cbor:"1,keyasint,omitempty"`
	Nonce             uint64 `cbor:"2,keyasint,omitempty"`
	Payload           []byte `cbor:"3,keyasint,omitempty"`
	ResetSessionToken []byte `cbor:"4,keyasint,omitempty"`
}

SessionPacket is a Noise protocol message, which may be a session handshake message, or secured application data, a SessionRoundTrip.

type SessionPrivateKey

type SessionPrivateKey [ed25519.PrivateKeySize]byte

SessionPrivateKey is a Noise protocol private key.

func GenerateSessionPrivateKey

func GenerateSessionPrivateKey() (SessionPrivateKey, error)

GenerateSessionPrivateKey creates a new session private key using crypto/rand.

GenerateSessionPrivateKey generates an Ed25519 private key, which is used directly for digital signatures and, when converted to Curve25519, as the Noise protocol ECDH private key.

The Ed25519 representation is the canonical representation since there's a 1:1 conversion from Ed25519 to Curve25519, but not the other way.

Digital signing use cases include signing a reset session token. In addition, externally, digital signing can be used in a challenge/response protocol that demonstrates ownership of a proxy private key corresponding to a claimed proxy public key.

func SessionPrivateKeyFromString

func SessionPrivateKeyFromString(s string) (SessionPrivateKey, error)

SessionPrivateKeyFromString returns a SessionPrivateKey given its base64 string encoding.

func (SessionPrivateKey) GetPublicKey

func (k SessionPrivateKey) GetPublicKey() (SessionPublicKey, error)

GetPublicKey returns the public key corresponding to the private key.

func (SessionPrivateKey) IsZero

func (k SessionPrivateKey) IsZero() bool

IsZero indicates if the private key is zero-value.

func (SessionPrivateKey) String

func (k SessionPrivateKey) String() string

String emits SessionPrivateKey as base64.

func (SessionPrivateKey) ToCurve25519

func (k SessionPrivateKey) ToCurve25519() []byte

ToCurve25519 converts the Ed25519 SessionPrivateKey to the unique corresponding Curve25519 private key for use in the Noise protocol.

type SessionPrologue

type SessionPrologue struct {
	SessionProtocolName    string `cbor:"1,keyasint,omitempty"`
	SessionProtocolVersion uint32 `cbor:"2,keyasint,omitempty"`
	SessionID              ID     `cbor:"3,keyasint,omitempty"`
}

SessionPrologue is a Noise protocol prologue, which binds the session ID to the session.

type SessionPublicKey

type SessionPublicKey [ed25519.PublicKeySize]byte

SessionPublicKey is a Noise protocol public key.

func SessionPublicKeyFromString

func SessionPublicKeyFromString(s string) (SessionPublicKey, error)

SessionPublicKeyFromString returns a SessionPublicKey given its base64 string encoding.

func SessionPublicKeysFromStrings

func SessionPublicKeysFromStrings(strs []string) ([]SessionPublicKey, error)

SessionPublicKeysFromStrings returns a list of SessionPublicKeys given the base64 string encodings.

func (SessionPublicKey) String

func (k SessionPublicKey) String() string

String emits SessionPublicKey as base64.

func (SessionPublicKey) ToCurve25519

ToCurve25519 converts the Ed25519 SessionPublicKey to the unique corresponding Curve25519 public key for use in the Noise protocol.

type SessionPublicKeyCurve25519

type SessionPublicKeyCurve25519 [curve25519.PointSize]byte

SessionPublicKeyCurve25519 is a representation of a Curve25519 public key as a fixed-size array that may be used as a map key.

func (SessionPublicKeyCurve25519) String

String emits SessionPublicKeyCurve25519 as base64.

type SessionRoundTrip

type SessionRoundTrip struct {
	RoundTripID ID     `cbor:"1,keyasint,omitempty"`
	Payload     []byte `cbor:"2,keyasint,omitempty"`
}

SessionRoundTrip is an application data request or response, which is secured by the Noise protocol session. Each request is assigned a unique RoundTripID, and each corresponding response has the same RoundTripID.

type WebRTCDialCoordinator

type WebRTCDialCoordinator interface {

	// Returns the network ID for the network this WebRTCDialCoordinator is
	// associated with. For a single WebRTCDialCoordinator, the NetworkID
	// value should not change. Replay-facilitating calls, Succeeded/Failed,
	// all assume the network and network ID remain static. The network ID
	// value is used by in-proxy dials to track internal state that depends
	// on the current network; this includes the port mapping types supported
	// by the network.
	NetworkID() string

	// Returns the network type for the current network, or NetworkTypeUnknown
	// if unknown.
	NetworkType() NetworkType

	// ClientRootObfuscationSecret is the root obfuscation secret generated by
	// or replayed by the client, which will be used to drive and replay
	// obfuscation operations for the WebRTC dial, including any DTLS
	// randomization. The proxy receives the same root obfuscation secret,
	// relayed by the broker, and so the client's selection drives
	// obfuscation/replay on both sides.
	ClientRootObfuscationSecret() ObfuscationSecret

	// DoDTLSRandomization indicates whether to perform DTLS
	// Client/ServerHello randomization. DoDTLSRandomization is specified by
	// clients, which may use a weighted coin flip or a replay to determine
	// the value.
	DoDTLSRandomization() bool

	// DataChannelTrafficShapingParameters returns parameters specifying how
	// to perform data channel traffic shapping -- random padding and decoy
	// message. Returns nil when no traffic shaping is to be performed.
	DataChannelTrafficShapingParameters() *DataChannelTrafficShapingParameters

	// STUNServerAddress selects a STUN server to use for this dial. When
	// RFC5780 is true, the STUN server must support RFC5780 NAT discovery;
	// otherwise, only basic STUN bind operation support is required. Clients
	// and proxies will receive a list of STUN server candidates via tactics,
	// and select a candidate at random or replay for each dial. If
	// STUNServerAddress returns "", STUN operations are skipped but the dial
	// may still succeed if a port mapping can be established.
	STUNServerAddress(RFC5780 bool) string

	// STUNServerAddressSucceeded is called after a successful STUN operation
	// with the STUN server specified by the address. This signal is used to
	// set replay for successful STUN servers. STUNServerAddressSucceeded
	// will be called when the STUN opertion succeeds, regardless of the
	// outcome of the rest of the dial. RFC5780 is true when the STUN server
	// was used for NAT discovery.
	STUNServerAddressSucceeded(RFC5780 bool, address string)

	// STUNServerAddressFailed is called after a failed STUN operation and is
	// used to clear replay for the specified STUN server.
	STUNServerAddressFailed(RFC5780 bool, address string)

	// DiscoverNAT indicates whether a client dial should start with NAT
	// discovery. Discovering and reporting the client NAT type will assist
	// in broker matching. However, RFC5780 NAT discovery can slow down a
	// dial and potentially looks like atypical network traffic. Client NAT
	// discovery is controlled by tactics and may be disabled or set to run
	// with a small probability. Discovered NAT types and portmapping types
	// may be cached and used with future dials via SetNATType/NATType and
	// SetPortMappingTypes/PortMappingTypes.
	//
	// Proxies always perform NAT discovery on start up, since that doesn't
	// delay a client dial.
	DiscoverNAT() bool

	// DisableSTUN indicates whether to skip STUN operations.
	DisableSTUN() bool

	// DisablePortMapping indicates whether to skip port mapping operations.
	DisablePortMapping() bool

	// DisableInboundForMobileNetworks indicates that all attempts to set up
	// inbound operations -- including STUN and port mapping -- should be
	// skipped when the network type is NetworkTypeMobile. This skips
	// operations that can slow down dials and and unlikely to succeed on
	// most mobile networks with CGNAT.
	DisableInboundForMobileNetworks() bool

	// DisableIPv6ICECandidates omits all IPv6 ICE candidates.
	DisableIPv6ICECandidates() bool

	// NATType returns any persisted NAT type for the current network, as set
	// by SetNATType. When NATTypeUnknown is returned, NAT discovery may be
	// run.
	NATType() NATType

	// SetNATType is called when the NAT type for the current network has been
	// discovered. The provider should persist this value, associated with
	// the current network ID and with a reasonable TTL, so the value can be
	// reused in subsequent dials without having to re-run NAT discovery.
	SetNATType(t NATType)

	// PortMappingTypes returns any persisted, supported port mapping types
	// for the current network, as set by SetPortMappingTypes. When an empty
	// list is returned port mapping discovery may be run. A list containing
	// only PortMappingTypeNone indicates that no supported port mapping
	// types were discovered.
	PortMappingTypes() PortMappingTypes

	// SetPortMappingTypes is called with the supported port mapping types
	// discovered for the current network. The provider should persist this
	// value, associated with the current network ID and with a reasonable
	// TTL, so the value can be reused in subsequent dials without having to
	// re-run port mapping discovery.
	SetPortMappingTypes(t PortMappingTypes)

	// ResolveAddress resolves a domain and returns its IP address. Clients
	// and proxies may use this to hook into the Psiphon custom resolver. The
	// provider adds the custom resolver tactics and network ID parameters
	// required by psiphon/common.Resolver.
	ResolveAddress(ctx context.Context, network, address string) (string, error)

	// UDPListen creates a local UDP socket. The socket should be bound to a
	// specific interface as required for VPN modes, and set a write timeout
	// to mitigate the issue documented in psiphon/common.WriteTimeoutUDPConn.
	UDPListen(ctx context.Context) (net.PacketConn, error)

	// UDPConn creates a local UDP socket "connected" to the specified remote
	// address. The socket should be excluded from VPN routing. This socket
	// is used to determine the local address of the active interface the OS
	// will select for the specified network ("udp4" for IPv4 or "udp6" for
	// IPv6) and remote destination. For this use case, the socket will not
	// be used to send network traffic.
	UDPConn(ctx context.Context, network, remoteAddress string) (net.PacketConn, error)

	// BindToDevice binds a socket, specified by the file descriptor, to an
	// interface that isn't routed through a VPN when Psiphon is running in
	// VPN mode. BindToDevice is used in cases where a custom dialer cannot
	// be used, and UDPListen cannot be called. If no file descriptor
	// operation is required, BindToDevice should take no action and return
	// nil.
	BindToDevice(fileDescriptor int) error

	// ProxyUpstreamDial is used by the proxy when dialing a TCP or UDP
	// upstream connection to a destination Psiphon server. This dial
	// callback allows for TCP/UDP-level dial tactics parameters to be
	// applied, as appropriate, to the upstream dial from the proxy vantage
	// point; and possible replay of those parameters. In addition,
	// underlying sockets should be bound to a specific interface as required
	// when the proxy app is also running a VPN.
	ProxyUpstreamDial(ctx context.Context, network, address string) (net.Conn, error)

	DiscoverNATTimeout() time.Duration
	WebRTCAnswerTimeout() time.Duration
	WebRTCAwaitDataChannelTimeout() time.Duration
	ProxyDestinationDialTimeout() time.Duration
}

WebRTCDialCoordinator provides in-proxy dial parameters and configuration, used by both clients and proxies, and an interface for signaling when parameters are successful or not, to facilitate replay of successful parameters.

Each WebRTCDialCoordinator should provide values selected in the context of a single network, as identified by a network ID. A distinct WebRTCDialCoordinator should be created for each client in-proxy dial, with new or replayed parameters selected as appropriate. One proxy run uses a single WebRTCDialCoordinator for all proxied connections. The proxy should be restarted with a new WebRTCDialCoordinator when the underlying network changes.

A WebRTCDialCoordinator implementation must be safe for concurrent calls.

type WebRTCSessionDescription

type WebRTCSessionDescription struct {
	Type int    `cbor:"1,keyasint,omitempty"`
	SDP  string `cbor:"2,keyasint,omitempty"`
}

WebRTCSessionDescription is compatible with pion/webrtc.SessionDescription and facilitates the PSIPHON_ENABLE_INPROXY build tag exclusion of pion dependencies.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL