Skip to content

Commit

Permalink
Merge branch 'master' into staging-client
Browse files Browse the repository at this point in the history
  • Loading branch information
rod-hynes committed Sep 4, 2024
2 parents 96d0a82 + 4a92fcd commit f5ba094
Show file tree
Hide file tree
Showing 45 changed files with 2,359 additions and 1,358 deletions.
14 changes: 9 additions & 5 deletions MobileLibrary/Android/PsiphonTunnel/PsiphonTunnel.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,14 @@ default public void onTrafficRateLimits(long upstreamBytesPerSecond, long downst
default public void onApplicationParameters(Object parameters) {}
default public void onServerAlert(String reason, String subject, List<String> actionURLs) {}
/**
* Called when tunnel-core emits a message to be displayed to the in-proxy operator.
* @param message The operator message received.
* Called when tunnel-core reports that a selected in-proxy mode --
* including running a proxy; or running a client in personal pairing
* mode -- cannot function without an app upgrade. The receiver
* should alert the user to upgrade the app and/or disable the
* unsupported mode(s). This callback is followed by a tunnel-core
* shutdown.
*/
default void onInproxyOperatorMessage(String message) {}
default void onInproxyMustUpgrade() {}
/**
* Called when tunnel-core reports proxy usage statistics.
* By default onInproxyProxyActivity is disabled. Enable it by setting
Expand Down Expand Up @@ -1115,8 +1119,8 @@ private void handlePsiphonNotice(String noticeJSON) {
notice.getJSONObject("data").getString("reason"),
notice.getJSONObject("data").getString("subject"),
actionURLsList);
} else if (noticeType.equals("InproxyOperatorMessage")) {
mHostService.onInproxyOperatorMessage( notice.getJSONObject("data").getString("message"));
} else if (noticeType.equals("InproxyMustUpgrade")) {
mHostService.onInproxyMustUpgrade();
} else if (noticeType.equals("InproxyProxyActivity")) {
JSONObject data = notice.getJSONObject("data");
mHostService.onInproxyProxyActivity(
Expand Down
9 changes: 6 additions & 3 deletions MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.h
Original file line number Diff line number Diff line change
Expand Up @@ -300,10 +300,13 @@ WWAN or vice versa or VPN state changed
- (void)onApplicationParameters:(NSDictionary * _Nonnull)parameters;

/*!
Called when tunnel-core emits a message to be displayed to the in-proxy operator
@param message The operator message received.
Called when tunnel-core reports that a selected in-proxy mode -- including
running a proxy; or running a client in personal pairing mode -- cannot
function without an app upgrade. The receiver should alert the user to
upgrade the app and/or disable the unsupported mode(s). This callback is
followed by a tunnel-core shutdown.
*/
- (void)onInproxyOperatorMessage:(NSString * _Nonnull)message;
- (void)onInproxyMustUpgrade;

/*!
Called when tunnel-core reports in-proxy usage statistics
Expand Down
11 changes: 3 additions & 8 deletions MobileLibrary/iOS/PsiphonTunnel/PsiphonTunnel/PsiphonTunnel.m
Original file line number Diff line number Diff line change
Expand Up @@ -1174,15 +1174,10 @@ - (void)handlePsiphonNotice:(NSString * _Nonnull)noticeJSON {
});
}
}
else if ([noticeType isEqualToString:@"InproxyOperatorMessage"]) {
id message = [notice valueForKeyPath:@"data.message"];
if (![message isKindOfClass:[NSString class]]) {
[self logMessage:[NSString stringWithFormat: @"InproxyOperatorMessage notice missing data.message: %@", noticeJSON]];
return;
}
if ([self.tunneledAppDelegate respondsToSelector:@selector(onInproxyOperatorMessage:)]) {
else if ([noticeType isEqualToString:@"InproxyMustUpgrade"]) {
if ([self.tunneledAppDelegate respondsToSelector:@selector(onInproxyMustUpgrade)]) {
dispatch_sync(self->callbackQueue, ^{
[self.tunneledAppDelegate onInproxyOperatorMessage:message];
[self.tunneledAppDelegate onInproxyMustUpgrade];
});
}
}
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ require (
github.com/florianl/go-nfqueue v1.1.1-0.20200829120558-a2f196e98ab0
github.com/flynn/noise v1.0.1-0.20220214164934-d803f5c4b0f4
github.com/fxamacker/cbor/v2 v2.5.0
github.com/gammazero/deque v0.2.1
github.com/gobwas/glob v0.2.4-0.20180402141543-f00a7392b439
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
github.com/google/gopacket v1.1.19
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,6 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/fxamacker/cbor/v2 v2.5.0 h1:oHsG0V/Q6E/wqTS2O1Cozzsy69nqCiguo5Q1a1ADivE=
github.com/fxamacker/cbor/v2 v2.5.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
Expand Down
85 changes: 72 additions & 13 deletions psiphon/common/inproxy/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,21 @@ import (
)

const (
ProxyProtocolVersion1 = 1
MaxCompartmentIDs = 10

// ProxyProtocolVersion1 represents protocol version 1.
ProxyProtocolVersion1 = int32(1)

// MinimumProxyProtocolVersion is the minimum supported version number.
MinimumProxyProtocolVersion = ProxyProtocolVersion1

MaxCompartmentIDs = 10
)

// proxyProtocolVersion is the current protocol version number.
// proxyProtocolVersion is variable, to enable overriding the value in tests.
// This value should not be overridden outside of test cases.
var proxyProtocolVersion = ProxyProtocolVersion1

// ID is a unique identifier used to identify inproxy connections and actors.
type ID [32]byte

Expand Down Expand Up @@ -243,14 +254,17 @@ type WebRTCSessionDescription struct {
// 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.
// MustUpgrade is an optional flag that is set by the broker, based on the
// submitted ProxyProtocolVersion, when the proxy app must be upgraded in
// order to function properly. Potential must-upgrade scenarios include
// changes to the personal pairing broker rendezvous algorithm, where no
// protocol backwards compatibility accommodations can ensure a rendezvous
// and match. When MustUpgrade is set, NoMatch is implied.
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"`
MustUpgrade bool `cbor:"13,keyasint,omitempty"`
ConnectionID ID `cbor:"5,keyasint,omitempty"`
ClientProxyProtocolVersion int32 `cbor:"6,keyasint,omitempty"`
ClientOfferSDP WebRTCSessionDescription `cbor:"7,keyasint,omitempty"`
Expand Down Expand Up @@ -322,9 +336,17 @@ type DataChannelTrafficShapingParameters struct {
// the broker using ClientRelayedPacketRequests and continues to relay using
// ClientRelayedPacketRequests until complete. ConnectionID identifies this
// connection and its relayed BrokerServerReport.
//
// MustUpgrade is an optional flag that is set by the broker, based on the
// submitted ProxyProtocolVersion, when the client app must be upgraded in
// order to function properly. Potential must-upgrade scenarios include
// changes to the personal pairing broker rendezvous algorithm, where no
// protocol backwards compatibility accommodations can ensure a rendezvous
// and match. When MustUpgrade is set, NoMatch is implied.
type ClientOfferResponse struct {
Limited bool `cbor:"1,keyasint,omitempty"`
NoMatch bool `cbor:"2,keyasint,omitempty"`
MustUpgrade bool `cbor:"7,keyasint,omitempty"`
ConnectionID ID `cbor:"3,keyasint,omitempty"`
SelectedProxyProtocolVersion int32 `cbor:"4,keyasint,omitempty"`
ProxyAnswerSDP WebRTCSessionDescription `cbor:"5,keyasint,omitempty"`
Expand Down Expand Up @@ -457,7 +479,7 @@ func (metrics *ProxyMetrics) ValidateAndGetParametersAndLogFields(
return nil, nil, errors.Trace(err)
}

if metrics.ProxyProtocolVersion != ProxyProtocolVersion1 {
if metrics.ProxyProtocolVersion < 0 || metrics.ProxyProtocolVersion > proxyProtocolVersion {
return nil, nil, errors.Tracef("invalid proxy protocol version: %v", metrics.ProxyProtocolVersion)
}

Expand Down Expand Up @@ -510,7 +532,7 @@ func (metrics *ClientMetrics) ValidateAndGetLogFields(
return nil, errors.Trace(err)
}

if metrics.ProxyProtocolVersion != ProxyProtocolVersion1 {
if metrics.ProxyProtocolVersion < 0 || metrics.ProxyProtocolVersion > proxyProtocolVersion {
return nil, errors.Tracef("invalid proxy protocol version: %v", metrics.ProxyProtocolVersion)
}

Expand Down Expand Up @@ -544,8 +566,12 @@ func (request *ProxyAnnounceRequest) ValidateAndGetParametersAndLogFields(
formatter common.APIParameterLogFieldFormatter,
geoIPData common.GeoIPData) (common.APIParameters, common.LogFields, error) {

if len(request.PersonalCompartmentIDs) > maxCompartmentIDs {
return nil, nil, errors.Tracef("invalid compartment IDs length: %d", len(request.PersonalCompartmentIDs))
// A proxy may specify at most 1 personal compartment ID. This is
// currently a limitation of the multi-queue implementation; see comment
// in announcementMultiQueue.enqueue.
if len(request.PersonalCompartmentIDs) > 1 {
return nil, nil, errors.Tracef(
"invalid compartment IDs length: %d", len(request.PersonalCompartmentIDs))
}

if request.Metrics == nil {
Expand Down Expand Up @@ -587,13 +613,31 @@ func (request *ClientOfferRequest) ValidateAndGetLogFields(
"invalid compartment IDs length: %d", len(request.PersonalCompartmentIDs))
}

if len(request.CommonCompartmentIDs) > 0 && len(request.PersonalCompartmentIDs) > 0 {
return nil, nil, errors.TraceNew("multiple compartment ID types")
}

// The client offer SDP may contain no ICE candidates.
errorOnNoCandidates := false

// The client offer SDP may include RFC 1918/4193 private IP addresses in
// personal pairing mode. filterSDPAddresses should not filter out
// private IP addresses based on the broker's local interfaces; this
// filtering occurs on the proxy that receives the SDP.
allowPrivateIPAddressCandidates :=
len(request.PersonalCompartmentIDs) > 0 &&
len(request.CommonCompartmentIDs) == 0
filterPrivateIPAddressCandidates := false

// Client offer SDP candidate addresses must match the country and ASN of
// the client. Don't facilitate connections to arbitrary destinations.
filteredSDP, sdpMetrics, err := filterSDPAddresses(
[]byte(request.ClientOfferSDP.SDP), errorOnNoCandidates, lookupGeoIP, geoIPData)
[]byte(request.ClientOfferSDP.SDP),
errorOnNoCandidates,
lookupGeoIP,
geoIPData,
allowPrivateIPAddressCandidates,
filterPrivateIPAddressCandidates)
if err != nil {
return nil, nil, errors.Trace(err)
}
Expand Down Expand Up @@ -637,6 +681,7 @@ func (request *ClientOfferRequest) ValidateAndGetLogFields(
logFields["has_personal_compartment_ids"] = hasPersonalCompartmentIDs
logFields["ice_candidate_types"] = request.ICECandidateTypes
logFields["has_IPv6"] = sdpMetrics.hasIPv6
logFields["has_private_IP"] = sdpMetrics.hasPrivateIP
logFields["filtered_ice_candidates"] = sdpMetrics.filteredICECandidates

return filteredSDP, logFields, nil
Expand Down Expand Up @@ -679,15 +724,28 @@ func (request *ProxyAnswerRequest) ValidateAndGetLogFields(
lookupGeoIP LookupGeoIP,
baseAPIParameterValidator common.APIParameterValidator,
formatter common.APIParameterLogFieldFormatter,
geoIPData common.GeoIPData) ([]byte, common.LogFields, error) {
geoIPData common.GeoIPData,
proxyAnnouncementHasPersonalCompartmentIDs bool) ([]byte, common.LogFields, error) {

// The proxy answer SDP must contain at least one ICE candidate.
errorOnNoCandidates := true

// The proxy answer SDP may include RFC 1918/4193 private IP addresses in
// personal pairing mode. filterSDPAddresses should not filter out
// private IP addresses based on the broker's local interfaces; this
// filtering occurs on the client that receives the SDP.
allowPrivateIPAddressCandidates := proxyAnnouncementHasPersonalCompartmentIDs
filterPrivateIPAddressCandidates := false

// Proxy answer SDP candidate addresses must match the country and ASN of
// the proxy. Don't facilitate connections to arbitrary destinations.
filteredSDP, sdpMetrics, err := filterSDPAddresses(
[]byte(request.ProxyAnswerSDP.SDP), errorOnNoCandidates, lookupGeoIP, geoIPData)
[]byte(request.ProxyAnswerSDP.SDP),
errorOnNoCandidates,
lookupGeoIP,
geoIPData,
allowPrivateIPAddressCandidates,
filterPrivateIPAddressCandidates)
if err != nil {
return nil, nil, errors.Trace(err)
}
Expand All @@ -712,6 +770,7 @@ func (request *ProxyAnswerRequest) ValidateAndGetLogFields(
logFields["connection_id"] = request.ConnectionID
logFields["ice_candidate_types"] = request.ICECandidateTypes
logFields["has_IPv6"] = sdpMetrics.hasIPv6
logFields["has_private_IP"] = sdpMetrics.hasPrivateIP
logFields["filtered_ice_candidates"] = sdpMetrics.filteredICECandidates
logFields["answer_error"] = request.AnswerError

Expand Down
49 changes: 48 additions & 1 deletion psiphon/common/inproxy/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,22 @@ func (b *Broker) handleProxyAnnounce(
return nil, errors.Trace(err)
}

// Return MustUpgrade when the proxy's protocol version is less than the
// minimum required.
if announceRequest.Metrics.ProxyProtocolVersion < MinimumProxyProtocolVersion {
responsePayload, err := MarshalProxyAnnounceResponse(
&ProxyAnnounceResponse{
NoMatch: true,
MustUpgrade: true,
})
if err != nil {
return nil, errors.Trace(err)
}

return responsePayload, nil

}

// Fetch new tactics for the proxy, if required, using the tactics tag
// that should be included with the API parameters. A tacticsPayload may
// be returned when there are no new tactics, and this is relayed back to
Expand Down Expand Up @@ -587,6 +603,9 @@ func (b *Broker) handleProxyAnnounce(
defer cancelFunc()
extendTransportTimeout(timeout)

// Note that matcher.Announce assumes a monotonically increasing
// announceCtx.Deadline input for each successive call.

clientOffer, matchMetrics, err = b.matcher.Announce(
announceCtx,
proxyIP,
Expand Down Expand Up @@ -768,6 +787,9 @@ func (b *Broker) handleClientOffer(
// processSDPAddresses), so all invalid candidates are removed and the
// remaining SDP is used. Filtered candidate information is logged in
// logFields.
//
// In personal pairing mode, RFC 1918/4193 private IP addresses are
// permitted in exchanged SDPs and not filtered out.

var filteredSDP []byte
filteredSDP, logFields, err = offerRequest.ValidateAndGetLogFields(
Expand Down Expand Up @@ -812,6 +834,21 @@ func (b *Broker) handleClientOffer(
return nil, errors.Trace(err)
}

// Return MustUpgrade when the client's protocol version is less than the
// minimum required.
if offerRequest.Metrics.ProxyProtocolVersion < MinimumProxyProtocolVersion {
responsePayload, err := MarshalClientOfferResponse(
&ClientOfferResponse{
NoMatch: true,
MustUpgrade: true,
})
if err != nil {
return nil, errors.Trace(err)
}

return responsePayload, nil
}

// Enqueue the client offer and await a proxy matching and subsequent
// proxy answer.

Expand Down Expand Up @@ -1020,13 +1057,23 @@ func (b *Broker) handleProxyAnswer(
// processSDPAddresses), so all invalid candidates are removed and the
// remaining SDP is used. Filtered candidate information is logged in
// logFields.
//
// In personal pairing mode, RFC 1918/4193 private IP addresses are
// permitted in exchanged SDPs and not filtered out.

hasPersonalCompartmentIDs, err := b.matcher.AnnouncementHasPersonalCompartmentIDs(
initiatorID, answerRequest.ConnectionID)
if err != nil {
return nil, errors.Trace(err)
}

var filteredSDP []byte
filteredSDP, logFields, err = answerRequest.ValidateAndGetLogFields(
b.config.LookupGeoIP,
b.config.APIParameterValidator,
b.config.APIParameterLogFieldFormatter,
geoIPData)
geoIPData,
hasPersonalCompartmentIDs)
if err != nil {
return nil, errors.Trace(err)
}
Expand Down
Loading

0 comments on commit f5ba094

Please sign in to comment.