feat(client): try restricted proxy if NAT unknown

Just once, to offload unrestricted proxies.
This is useful when our STUN servers are blocked or don't support
the NAT discovery feature, or if they're just slow.

Closes https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/issues/40178.
Partially addresses https://gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/-/issues/40376

Signed-off-by: Cecylia Bocovich <cohosh@torproject.org>
This commit is contained in:
WofWca 2024-12-04 19:14:46 +04:00 committed by Cecylia Bocovich
parent f6767061e4
commit 94b6647d33
No known key found for this signature in database
GPG key ID: 009DE379FD9B7B90
4 changed files with 163 additions and 27 deletions

View file

@ -45,28 +45,43 @@ type WebRTCPeer struct {
proxy *url.URL
}
// Deprecated: Use NewWebRTCPeerWithEventsAndProxy Instead.
// Deprecated: Use NewWebRTCPeerWithNatPolicyAndEventsAndProxy Instead.
func NewWebRTCPeer(
config *webrtc.Configuration, broker *BrokerChannel,
) (*WebRTCPeer, error) {
return NewWebRTCPeerWithEventsAndProxy(config, broker, nil, nil)
return NewWebRTCPeerWithNatPolicyAndEventsAndProxy(
config, broker, nil, nil, nil,
)
}
// Deprecated: Use NewWebRTCPeerWithEventsAndProxy Instead.
// Deprecated: Use NewWebRTCPeerWithNatPolicyAndEventsAndProxy Instead.
func NewWebRTCPeerWithEvents(
config *webrtc.Configuration, broker *BrokerChannel,
eventsLogger event.SnowflakeEventReceiver,
) (*WebRTCPeer, error) {
return NewWebRTCPeerWithEventsAndProxy(config, broker, eventsLogger, nil)
return NewWebRTCPeerWithNatPolicyAndEventsAndProxy(
config, broker, nil, eventsLogger, nil,
)
}
// NewWebRTCPeerWithEventsAndProxy constructs a WebRTC PeerConnection to a snowflake proxy.
// Deprecated: Use NewWebRTCPeerWithNatPolicyAndEventsAndProxy Instead.
func NewWebRTCPeerWithEventsAndProxy(
config *webrtc.Configuration, broker *BrokerChannel,
eventsLogger event.SnowflakeEventReceiver, proxy *url.URL,
) (*WebRTCPeer, error) {
return NewWebRTCPeerWithNatPolicyAndEventsAndProxy(
config, broker, nil, eventsLogger, proxy,
)
}
// NewWebRTCPeerWithNatPolicyAndEventsAndProxy constructs
// a WebRTC PeerConnection to a snowflake proxy.
//
// The creation of the peer handles the signaling to the Snowflake broker, including
// the exchange of SDP information, the creation of a PeerConnection, and the establishment
// of a DataChannel to the Snowflake proxy.
func NewWebRTCPeerWithEventsAndProxy(
config *webrtc.Configuration, broker *BrokerChannel,
func NewWebRTCPeerWithNatPolicyAndEventsAndProxy(
config *webrtc.Configuration, broker *BrokerChannel, natPolicy *NATPolicy,
eventsLogger event.SnowflakeEventReceiver, proxy *url.URL,
) (*WebRTCPeer, error) {
if eventsLogger == nil {
@ -92,7 +107,7 @@ func NewWebRTCPeerWithEventsAndProxy(
connection.eventsLogger = eventsLogger
connection.proxy = proxy
err := connection.connect(config, broker)
err := connection.connect(config, broker, natPolicy)
if err != nil {
connection.Close()
return nil, err
@ -165,8 +180,15 @@ func (c *WebRTCPeer) checkForStaleness(timeout time.Duration) {
}
// connect does the bulk of the work: gather ICE candidates, send the SDP offer to broker,
// receive an answer from broker, and wait for data channel to open
func (c *WebRTCPeer) connect(config *webrtc.Configuration, broker *BrokerChannel) error {
// receive an answer from broker, and wait for data channel to open.
//
// `natPolicy` can be nil, in which case we'll always send our actual
// NAT type to the broker.
func (c *WebRTCPeer) connect(
config *webrtc.Configuration,
broker *BrokerChannel,
natPolicy *NATPolicy,
) error {
log.Println(c.id, " connecting...")
err := c.preparePeerConnection(config, broker.keepLocalAddresses)
@ -179,7 +201,24 @@ func (c *WebRTCPeer) connect(config *webrtc.Configuration, broker *BrokerChannel
return err
}
answer, err := broker.Negotiate(localDescription)
actualNatType := broker.GetNATType()
var natTypeToSend string
if natPolicy != nil {
natTypeToSend = natPolicy.NATTypeToSend(actualNatType)
} else {
natTypeToSend = actualNatType
}
if natTypeToSend != actualNatType {
log.Printf(
"Our NAT type is \"%v\", but let's tell the broker it's \"%v\".",
actualNatType,
natTypeToSend,
)
} else {
log.Printf("natTypeToSend: \"%v\" (same as actualNatType)", natTypeToSend)
}
answer, err := broker.Negotiate(localDescription, natTypeToSend)
c.eventsLogger.OnNewSnowflakeEvent(event.EventOnBrokerRendezvous{
WebRTCRemoteDescription: answer,
Error: err,
@ -197,9 +236,15 @@ func (c *WebRTCPeer) connect(config *webrtc.Configuration, broker *BrokerChannel
// Wait for the datachannel to open or time out
select {
case <-c.open:
if natPolicy != nil {
natPolicy.Success(actualNatType, natTypeToSend)
}
case <-time.After(DataChannelTimeout):
c.transport.Close()
err = errors.New("timeout waiting for DataChannel.OnOpen")
if natPolicy != nil {
natPolicy.Failure(actualNatType, natTypeToSend)
}
c.eventsLogger.OnNewSnowflakeEvent(event.EventOnSnowflakeConnectionFailed{Error: err})
return err
}