rendezvous.go 4.48 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
18
19
	"net/http"
	"net/url"

20
	"github.com/pion/webrtc"
21
22
)

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

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

39
40
41
42
43
44
45
46
47
// 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
}

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

65
	bc.transport = transport
66
	return bc
67
68
}

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

79
80
// Roundtrip HTTP POST using WebRTC SessionDescriptions.
//
81
// Send an SDP offer to the broker, which assigns a proxy and responds
82
// with an SDP answer from a designated remote WebRTC peer.
83
func (bc *BrokerChannel) Negotiate(offer *webrtc.SessionDescription) (
84
	*webrtc.SessionDescription, error) {
85
86
	log.Println("Negotiating via BrokerChannel...\nTarget URL: ",
		bc.Host, "\nFront URL:  ", bc.url.Host)
87
	data := bytes.NewReader([]byte(serializeSessionDescription(offer)))
88
	// Suffix with broker's client registration handler.
89
90
	clientURL := bc.url.ResolveReference(&url.URL{Path: "client"})
	request, err := http.NewRequest("POST", clientURL.String(), data)
91
92
93
	if nil != err {
		return nil, err
	}
94
95
	if "" != bc.Host { // Set true host if necessary.
		request.Host = bc.Host
96
	}
97
	resp, err := bc.transport.RoundTrip(request)
98
99
100
101
	if nil != err {
		return nil, err
	}
	defer resp.Body.Close()
102
	log.Printf("BrokerChannel Response:\n%s\n\n", resp.Status)
103
104
105

	switch resp.StatusCode {
	case http.StatusOK:
106
		body, err := limitedRead(resp.Body, readLimit)
107
108
109
		if nil != err {
			return nil, err
		}
110
		answer := deserializeSessionDescription(string(body))
111
112
113
		return answer, nil

	case http.StatusServiceUnavailable:
114
		return nil, errors.New(BrokerError503)
115
	case http.StatusBadRequest:
116
		return nil, errors.New(BrokerError400)
117
	default:
118
		return nil, errors.New(BrokerErrorUnexpected)
119
	}
120
}
121
122
123
124

// Implements the |Tongue| interface to catch snowflakes, using BrokerChannel.
type WebRTCDialer struct {
	*BrokerChannel
125
	webrtcConfig *webrtc.Configuration
126
127
128
}

func NewWebRTCDialer(
129
130
131
132
133
134
135
136
	broker *BrokerChannel, iceServers []webrtc.ICEServer) *WebRTCDialer {
	var config webrtc.Configuration
	if iceServers == nil {
		config = webrtc.Configuration{
			ICEServers: iceServers,
		}
	} else {
		config = webrtc.Configuration{}
137
	}
138
139
	return &WebRTCDialer{
		BrokerChannel: broker,
140
		webrtcConfig:  &config,
141
142
143
144
	}
}

// Initialize a WebRTC Connection by signaling through the broker.
145
func (w WebRTCDialer) Catch() (Snowflake, error) {
146
147
148
149
150
	if nil == w.BrokerChannel {
		return nil, errors.New("Cannot Dial WebRTC without a BrokerChannel.")
	}
	// TODO: [#3] Fetch ICE server information from Broker.
	// TODO: [#18] Consider TURN servers here too.
151
	connection := NewWebRTCPeer(w.webrtcConfig, w.BrokerChannel)
152
153
154
	err := connection.Connect()
	return connection, err
}