Skip to content

Commit

Permalink
Merge branch 'master' into staging-server
Browse files Browse the repository at this point in the history
  • Loading branch information
rod-hynes committed Aug 26, 2024
2 parents 13b56aa + a8413df commit 1738bc5
Show file tree
Hide file tree
Showing 42 changed files with 1,964 additions and 1,123 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
66 changes: 57 additions & 9 deletions psiphon/common/inproxy/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,14 +243,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 +325,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 @@ -544,8 +555,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 +602,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 +670,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 +713,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 +759,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
18 changes: 17 additions & 1 deletion psiphon/common/inproxy/broker.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,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 +771,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 @@ -1020,13 +1026,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
38 changes: 31 additions & 7 deletions psiphon/common/inproxy/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,16 @@ type ClientConfig struct {
// with the caller invoking ServerEntryFields.RemoveUnsignedFields to
// prune local, unnsigned fields before sending.
PackedDestinationServerEntry []byte

// MustUpgrade is a callback that is invoked when a MustUpgrade flag is
// received from the broker. When MustUpgrade is received, the client
// should be stopped and the user should be prompted to upgrade before
// restarting the client.
//
// In Psiphon, MustUpgrade may be ignored when not running in
// in-proxy-only personal pairing mode, as other tunnel protocols remain
// available.
MustUpgrade func()
}

// DialClient establishes an in-proxy connection for relaying traffic to the
Expand Down Expand Up @@ -314,6 +324,13 @@ func dialClientWebRTCConn(
ctx context.Context,
config *ClientConfig) (retResult *clientWebRTCDialResult, retRetry bool, retErr error) {

brokerCoordinator := config.BrokerClient.GetBrokerDialCoordinator()
personalCompartmentIDs := brokerCoordinator.PersonalCompartmentIDs()

// In personal pairing mode, RFC 1918/4193 private IP addresses are
// included in SDPs.
hasPersonalCompartmentIDs := len(personalCompartmentIDs) > 0

// Initialize the WebRTC offer

doTLSRandomization := config.WebRTCDialCoordinator.DoDTLSRandomization()
Expand All @@ -329,7 +346,8 @@ func dialClientWebRTCConn(
DoDTLSRandomization: doTLSRandomization,
TrafficShapingParameters: trafficShapingParameters,
ReliableTransport: config.ReliableTransport,
})
},
hasPersonalCompartmentIDs)
if err != nil {
return nil, true, errors.Trace(err)
}
Expand All @@ -342,8 +360,6 @@ func dialClientWebRTCConn(

// Send the ClientOffer request to the broker

brokerCoordinator := config.BrokerClient.GetBrokerDialCoordinator()

packedBaseParams, err := protocol.EncodePackedAPIParameters(config.BaseAPIParameters)
if err != nil {
return nil, false, errors.Trace(err)
Expand All @@ -366,7 +382,7 @@ func dialClientWebRTCConn(
PortMappingTypes: config.WebRTCDialCoordinator.PortMappingTypes(),
},
CommonCompartmentIDs: brokerCoordinator.CommonCompartmentIDs(),
PersonalCompartmentIDs: brokerCoordinator.PersonalCompartmentIDs(),
PersonalCompartmentIDs: personalCompartmentIDs,
ClientOfferSDP: SDP,
ICECandidateTypes: SDPMetrics.iceCandidateTypes,
ClientRootObfuscationSecret: clientRootObfuscationSecret,
Expand All @@ -380,8 +396,8 @@ func dialClientWebRTCConn(
return nil, false, errors.Trace(err)
}

// No retry when rate/entry limited; do retry on no-match, as a match may
// soon appear.
// No retry when rate/entry limited or must upgrade; do retry on no-match,
// as a match may soon appear.

if offerResponse.Limited {
return nil, false, errors.TraceNew("limited")
Expand All @@ -390,6 +406,13 @@ func dialClientWebRTCConn(

return nil, true, errors.TraceNew("no proxy match")

} else if offerResponse.MustUpgrade {

if config.MustUpgrade != nil {
config.MustUpgrade()
}

return nil, false, errors.TraceNew("must upgrade")
}

if offerResponse.SelectedProxyProtocolVersion != ProxyProtocolVersion1 {
Expand All @@ -402,7 +425,8 @@ func dialClientWebRTCConn(

// Establish the WebRTC DataChannel connection

err = webRTCConn.SetRemoteSDP(offerResponse.ProxyAnswerSDP)
err = webRTCConn.SetRemoteSDP(
offerResponse.ProxyAnswerSDP, hasPersonalCompartmentIDs)
if err != nil {
return nil, true, errors.Trace(err)
}
Expand Down
Loading

0 comments on commit 1738bc5

Please sign in to comment.