rendezvous.go 6.04 KB
Newer Older
1
2
3
// WebRTC rendezvous requires the exchange of SessionDescriptions between
// peers in order to establish a PeerConnection.
//
Arlo Breault's avatar
Arlo Breault committed
4
// This file contains the one method currently available to Snowflake:
5
6
7
//
// - Domain-fronted HTTP signaling. The Broker automatically exchange offers
//   and answers between this client and some remote WebRTC proxy.
Arlo Breault's avatar
Arlo Breault committed
8

9
package lib
10
11
12

import (
	"bytes"
13
	"errors"
14
	"io"
15
	"io/ioutil"
16
	"log"
17
	"net"
18
19
20
	"net/http"
	"net/url"

21
	"git.torproject.org/pluggable-transports/snowflake.git/common/util"
22
23
	"github.com/pion/sdp/v2"
	"github.com/pion/webrtc/v2"
24
25
)

26
27
28
29
const (
	BrokerError503        string = "No snowflake proxies currently available."
	BrokerError400        string = "You sent an invalid offer in the request."
	BrokerErrorUnexpected string = "Unexpected error, no answer."
30
	readLimit                    = 100000 //Maximum number of bytes to be read from an HTTP response
31
32
)

33
34
// Signalling Channel to the Broker.
type BrokerChannel struct {
35
36
	// The Host header to put in the HTTP request (optional and may be
	// different from the host name in URL).
37
38
39
40
	Host               string
	url                *url.URL
	transport          http.RoundTripper // Used to make all requests.
	keepLocalAddresses bool
41
42
}

43
44
45
46
47
48
49
50
51
// We make a copy of DefaultTransport because we want the default Dial
// and TLSHandshakeTimeout settings. But we want to disable the default
// ProxyFromEnvironment setting.
func CreateBrokerTransport() http.RoundTripper {
	transport := http.DefaultTransport.(*http.Transport)
	transport.Proxy = nil
	return transport
}

52
// Construct a new BrokerChannel, where:
53
54
// |broker| is the full URL of the facilitating program which assigns proxies
// to clients, and |front| is the option fronting domain.
55
func NewBrokerChannel(broker string, front string, transport http.RoundTripper, keepLocalAddresses bool) (*BrokerChannel, error) {
56
	targetURL, err := url.Parse(broker)
David Fifield's avatar
David Fifield committed
57
	if err != nil {
58
		return nil, err
59
	}
60
	log.Println("Rendezvous using Broker at:", broker)
61
62
	bc := new(BrokerChannel)
	bc.url = targetURL
David Fifield's avatar
David Fifield committed
63
	if front != "" { // Optional front domain.
64
		log.Println("Domain fronting using:", front)
65
66
		bc.Host = bc.url.Host
		bc.url.Host = front
67
	}
68

69
	bc.transport = transport
70
	bc.keepLocalAddresses = keepLocalAddresses
71
	return bc, nil
72
73
}

74
func limitedRead(r io.Reader, limit int64) ([]byte, error) {
75
	p, err := ioutil.ReadAll(&io.LimitedReader{R: r, N: limit + 1})
76
77
	if err != nil {
		return p, err
78
79
	} else if int64(len(p)) == limit+1 {
		return p[0:limit], io.ErrUnexpectedEOF
80
81
82
83
	}
	return p, err
}

84
85
86
87
88
89
90
91
92
93
94
95
96
97
// Stolen from https://github.com/golang/go/pull/30278
func IsLocal(ip net.IP) bool {
	if ip4 := ip.To4(); ip4 != nil {
		// Local IPv4 addresses are defined in https://tools.ietf.org/html/rfc1918
		return ip4[0] == 10 ||
			(ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
			(ip4[0] == 192 && ip4[1] == 168)
	}
	// Local IPv6 addresses are defined in https://tools.ietf.org/html/rfc4193
	return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc
}

// Removes local LAN address ICE candidates
func stripLocalAddresses(str string) string {
98
99
100
101
102
103
104
105
106
107
	var desc sdp.SessionDescription
	err := desc.Unmarshal([]byte(str))
	if err != nil {
		return str
	}
	for _, m := range desc.MediaDescriptions {
		attrs := make([]sdp.Attribute, 0)
		for _, a := range m.Attributes {
			if a.IsICECandidate() {
				ice, err := a.ToICECandidate()
108
				if err == nil && ice.Typ == "host" {
109
					ip := net.ParseIP(ice.Address)
110
111
					if ip != nil && (IsLocal(ip) || ip.IsUnspecified() || ip.IsLoopback()) {
						/* no append in this case */
112
113
114
						continue
					}
				}
115
			}
116
			attrs = append(attrs, a)
117
		}
118
119
120
121
122
123
124
		m.Attributes = attrs
	}
	bts, err := desc.Marshal()
	if err != nil {
		return str
	}
	return string(bts)
125
126
}

127
128
// Roundtrip HTTP POST using WebRTC SessionDescriptions.
//
129
// Send an SDP offer to the broker, which assigns a proxy and responds
130
// with an SDP answer from a designated remote WebRTC peer.
131
func (bc *BrokerChannel) Negotiate(offer *webrtc.SessionDescription) (
132
	*webrtc.SessionDescription, error) {
133
134
	log.Println("Negotiating via BrokerChannel...\nTarget URL: ",
		bc.Host, "\nFront URL:  ", bc.url.Host)
135
136
137
138
	// Ideally, we could specify an `RTCIceTransportPolicy` that would handle
	// this for us.  However, "public" was removed from the draft spec.
	// See https://developer.mozilla.org/en-US/docs/Web/API/RTCConfiguration#RTCIceTransportPolicy_enum
	if !bc.keepLocalAddresses {
139
140
141
142
		offer = &webrtc.SessionDescription{
			Type: offer.Type,
			SDP:  stripLocalAddresses(offer.SDP),
		}
143
	}
144
	data := bytes.NewReader([]byte(util.SerializeSessionDescription(offer)))
145
	// Suffix with broker's client registration handler.
146
147
	clientURL := bc.url.ResolveReference(&url.URL{Path: "client"})
	request, err := http.NewRequest("POST", clientURL.String(), data)
148
149
150
	if nil != err {
		return nil, err
	}
151
152
	if "" != bc.Host { // Set true host if necessary.
		request.Host = bc.Host
153
	}
154
	resp, err := bc.transport.RoundTrip(request)
155
156
157
158
	if nil != err {
		return nil, err
	}
	defer resp.Body.Close()
159
	log.Printf("BrokerChannel Response:\n%s\n\n", resp.Status)
160
161
162

	switch resp.StatusCode {
	case http.StatusOK:
163
		body, err := limitedRead(resp.Body, readLimit)
164
165
166
		if nil != err {
			return nil, err
		}
167
		answer := util.DeserializeSessionDescription(string(body))
168
169
		return answer, nil
	case http.StatusServiceUnavailable:
170
		return nil, errors.New(BrokerError503)
171
	case http.StatusBadRequest:
172
		return nil, errors.New(BrokerError400)
173
	default:
174
		return nil, errors.New(BrokerErrorUnexpected)
175
	}
176
}
177
178
179
180

// Implements the |Tongue| interface to catch snowflakes, using BrokerChannel.
type WebRTCDialer struct {
	*BrokerChannel
181
	webrtcConfig *webrtc.Configuration
182
183
}

David Fifield's avatar
David Fifield committed
184
func NewWebRTCDialer(broker *BrokerChannel, iceServers []webrtc.ICEServer) *WebRTCDialer {
David Fifield's avatar
David Fifield committed
185
186
	config := webrtc.Configuration{
		ICEServers: iceServers,
187
	}
188
189
	return &WebRTCDialer{
		BrokerChannel: broker,
190
		webrtcConfig:  &config,
191
192
193
194
	}
}

// Initialize a WebRTC Connection by signaling through the broker.
195
func (w WebRTCDialer) Catch() (Snowflake, error) {
196
197
	// TODO: [#3] Fetch ICE server information from Broker.
	// TODO: [#18] Consider TURN servers here too.
198
	connection := NewWebRTCPeer(w.webrtcConfig, w.BrokerChannel)
199
200
201
	err := connection.Connect()
	return connection, err
}