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
- Variables
- func Enabled() bool
- func GetAllowBogonWebRTCConnections() bool
- func GetAllowCommonASNMatching() bool
- func HaveCommonIDs(a, b []ID) bool
- func MarshalBrokerServerReport(request *BrokerServerReport) ([]byte, error)
- func MarshalClientOfferRequest(request *ClientOfferRequest) ([]byte, error)
- func MarshalClientOfferResponse(response *ClientOfferResponse) ([]byte, error)
- func MarshalClientRelayedPacketRequest(request *ClientRelayedPacketRequest) ([]byte, error)
- func MarshalClientRelayedPacketResponse(response *ClientRelayedPacketResponse) ([]byte, error)
- func MarshalProxyAnnounceRequest(request *ProxyAnnounceRequest) ([]byte, error)
- func MarshalProxyAnnounceResponse(response *ProxyAnnounceResponse) ([]byte, error)
- func MarshalProxyAnswerRequest(request *ProxyAnswerRequest) ([]byte, error)
- func MarshalProxyAnswerResponse(response *ProxyAnswerResponse) ([]byte, error)
- func NATDiscover(ctx context.Context, config *NATDiscoverConfig)
- func SetAllowBogonWebRTCConnections(allow bool)
- func SetAllowCommonASNMatching(allow bool)
- type ActivityUpdater
- type Broker
- func (b *Broker) HandleSessionPacket(ctx context.Context, extendTransportTimeout ExtendTransportTimeout, ...) ([]byte, error)
- func (b *Broker) SetCommonCompartmentIDs(commonCompartmentIDs []ID) error
- func (b *Broker) SetLimits(matcherAnnouncementLimitEntryCount int, ...)
- func (b *Broker) SetTimeouts(proxyAnnounceTimeout time.Duration, clientOfferTimeout time.Duration, ...)
- func (b *Broker) Start() error
- func (b *Broker) Stop()
- type BrokerClient
- func (b *BrokerClient) ClientOffer(ctx context.Context, request *ClientOfferRequest) (*ClientOfferResponse, error)
- func (b *BrokerClient) ClientRelayedPacket(ctx context.Context, request *ClientRelayedPacketRequest) (*ClientRelayedPacketResponse, error)
- func (b *BrokerClient) GetBrokerDialCoordinator() BrokerDialCoordinator
- func (b *BrokerClient) ProxyAnnounce(ctx context.Context, requestDelay time.Duration, request *ProxyAnnounceRequest) (*ProxyAnnounceResponse, error)
- func (b *BrokerClient) ProxyAnswer(ctx context.Context, request *ProxyAnswerRequest) (*ProxyAnswerResponse, error)
- type BrokerConfig
- type BrokerDialCoordinator
- type BrokerServerReport
- type ClientConfig
- type ClientConn
- func (conn *ClientConn) Close() error
- func (conn *ClientConn) GetConnectionID() ID
- func (conn *ClientConn) GetMetrics() common.LogFields
- func (conn *ClientConn) InitialRelayPacket() []byte
- func (conn *ClientConn) IsClosed() bool
- func (conn *ClientConn) LocalAddr() net.Addr
- func (conn *ClientConn) Read(p []byte) (int, error)
- func (conn *ClientConn) ReadFrom(b []byte) (int, net.Addr, error)
- func (conn *ClientConn) RelayPacket(ctx context.Context, in []byte) ([]byte, error)
- func (conn *ClientConn) RemoteAddr() net.Addr
- func (conn *ClientConn) SetDeadline(t time.Time) error
- func (conn *ClientConn) SetReadDeadline(t time.Time) error
- func (conn *ClientConn) SetWriteDeadline(t time.Time) error
- func (conn *ClientConn) Write(p []byte) (int, error)
- func (conn *ClientConn) WriteTo(b []byte, _ net.Addr) (int, error)
- type ClientMetrics
- type ClientOfferRequest
- type ClientOfferResponse
- type ClientRelayedPacketRequest
- type ClientRelayedPacketResponse
- type DataChannelTrafficShapingParameters
- type ExtendTransportTimeout
- type GetTactics
- type ICECandidateType
- type ICECandidateTypes
- type ID
- type InitiatorRoundTrip
- type InitiatorSessions
- type LookupGeoIP
- type MatchAnnouncement
- type MatchAnswer
- type MatchMetrics
- type MatchOffer
- type MatchProperties
- func (p *MatchProperties) EffectiveNATType() NATType
- func (p *MatchProperties) ExistsPreferredNATMatch(unlimitedNAT, partiallyLimitedNAT, limitedNAT bool) bool
- func (p *MatchProperties) IsPersonalCompartmentalized() bool
- func (p *MatchProperties) IsPreferredNATMatch(peerMatchProperties *MatchProperties) bool
- type Matcher
- func (m *Matcher) Announce(ctx context.Context, proxyIP string, proxyAnnouncement *MatchAnnouncement) (*MatchOffer, *MatchMetrics, error)
- func (m *Matcher) Answer(proxyAnswer *MatchAnswer) error
- func (m *Matcher) AnswerError(proxyID ID, connectionID ID)
- func (m *Matcher) Offer(ctx context.Context, clientIP string, clientOffer *MatchOffer) (*MatchAnswer, *MatchAnnouncement, *MatchMetrics, error)
- func (m *Matcher) SetLimits(announcementLimitEntryCount int, announcementRateLimitQuantity int, ...)
- func (m *Matcher) Start() error
- func (m *Matcher) Stop()
- type MatcherConfig
- type MatcherLimitError
- type NATDiscoverConfig
- type NATFiltering
- type NATMapping
- type NATTraversal
- type NATType
- func (t NATType) Compatible(t1 NATType) bool
- func (t NATType) ExistsPreferredMatch(unlimited, partiallyLimited, limited bool) bool
- func (t NATType) Filtering() NATFiltering
- func (t NATType) IsPreferredMatch(t1 NATType) bool
- func (t NATType) IsValid() bool
- func (t NATType) Mapping() NATMapping
- func (t NATType) MarshalText() ([]byte, error)
- func (t NATType) NeedsDiscovery() bool
- func (t NATType) String() string
- func (t NATType) Traversal() NATTraversal
- type NetworkProtocol
- type NetworkType
- type ObfuscationSecret
- type PortMappingType
- type PortMappingTypes
- type ProxiedConnectionHandler
- type Proxy
- type ProxyAnnounceRequest
- type ProxyAnnounceResponse
- type ProxyAnswerRequest
- type ProxyAnswerResponse
- type ProxyConfig
- type ProxyMetrics
- type RequestHandler
- type ResponderSessions
- type RoundTripper
- type RoundTripperFailedError
- type ServerBrokerSessions
- type SessionPacket
- type SessionPrivateKey
- type SessionPrologue
- type SessionPublicKey
- type SessionPublicKeyCurve25519
- type SessionRoundTrip
- type WebRTCDialCoordinator
- type WebRTCSessionDescription
Constants ¶
const ( ProxyProtocolVersion1 = 1 MaxCompartmentIDs = 10 )
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" )
const ( SessionProtocolName = "psiphon-inproxy-session" SessionProtocolVersion1 = 1 )
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 ¶
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 GetAllowBogonWebRTCConnections ¶
func GetAllowBogonWebRTCConnections() bool
func GetAllowCommonASNMatching ¶
func GetAllowCommonASNMatching() bool
func HaveCommonIDs ¶
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 ¶
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.
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) RelayPacket ¶
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
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 ¶
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 ¶
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 ¶
IDFromString returns an ID given its string encoding.
func IDsFromStrings ¶
IDsFromStrings returns a list of IDs given a list of string encodings.
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 ¶
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 (*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 ¶
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 ¶
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 ¶
Compatible indicates whether the NATType NATTraversals are compatible.
func (NATType) ExistsPreferredMatch ¶
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 ¶
IsPreferredMatch indicates whether the peer NATType's NATTraversal is preferred.
func (NATType) Mapping ¶
func (t NATType) Mapping() NATMapping
Mapping extracts the NATMapping component of this NATType.
func (NATType) MarshalText ¶
MarshalText ensures the string representation of the value is logged in JSON.
func (NATType) NeedsDiscovery ¶
NeedsDiscovery indicates that the NATType is unknown and should be discovered.
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 ¶
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 ¶
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 ¶
func (k SessionPublicKey) ToCurve25519() (SessionPublicKeyCurve25519, error)
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 ¶
func (k SessionPublicKeyCurve25519) String() 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.