snowflake.go 17.3 KB
Newer Older
1
2
3
4
package main

import (
	"bytes"
David Fifield's avatar
fmt    
David Fifield committed
5
	"crypto/rand"
6
7
8
9
10
11
12
13
14
	"encoding/base64"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/url"
David Fifield's avatar
David Fifield committed
15
	"os"
16
	"regexp"
17
18
19
20
	"strings"
	"sync"
	"time"

21
	"git.torproject.org/pluggable-transports/snowflake.git/common/messages"
22
	"git.torproject.org/pluggable-transports/snowflake.git/common/safelog"
23
	"git.torproject.org/pluggable-transports/snowflake.git/common/util"
24
25
	"git.torproject.org/pluggable-transports/snowflake.git/common/websocketconn"
	"github.com/gorilla/websocket"
Cecylia Bocovich's avatar
Cecylia Bocovich committed
26
27
	"github.com/pion/ice/v2"
	"github.com/pion/sdp/v3"
28
	"github.com/pion/webrtc/v3"
29
30
)

David Fifield's avatar
David Fifield committed
31
const defaultBrokerURL = "https://snowflake-broker.bamsoftware.com/"
32
const defaultProbeURL = "https://snowflake-broker.torproject.net:8443/probe"
33
const defaultRelayURL = "wss://snowflake.bamsoftware.com/"
34
const defaultSTUNURL = "stun:stun.stunprotocol.org:3478"
35
const pollInterval = 5 * time.Second
36
37
38
39
40
const (
	NATUnknown      = "unknown"
	NATRestricted   = "restricted"
	NATUnrestricted = "unrestricted"
)
41

42
43
//amount of time after sending an SDP answer before the proxy assumes the
//client is not going to connect
Cecylia Bocovich's avatar
Cecylia Bocovich committed
44
const dataChannelTimeout = 20 * time.Second
45

46
47
const readLimit = 100000 //Maximum number of bytes to be read from an HTTP request

48
var broker *SignalingServer
49
var relayURL string
50

51
52
var currentNATType = NATUnknown

53
54
55
56
57
58
const (
	sessionIDLength = 16
)

var (
	tokens chan bool
59
	config webrtc.Configuration
60
61
62
	client http.Client
)

63
64
65
66
67
68
69
var remoteIPPatterns = []*regexp.Regexp{
	/* IPv4 */
	regexp.MustCompile(`(?m)^c=IN IP4 ([\d.]+)(?:(?:\/\d+)?\/\d+)?(:? |\r?\n)`),
	/* IPv6 */
	regexp.MustCompile(`(?m)^c=IN IP6 ([0-9A-Fa-f:.]+)(?:\/\d+)?(:? |\r?\n)`),
}

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// Checks whether an IP address is a remote address for the client
func isRemoteAddress(ip net.IP) bool {
	return !(util.IsLocal(ip) || ip.IsUnspecified() || ip.IsLoopback())
}

func remoteIPFromSDP(str string) net.IP {
	// Look for remote IP in "a=candidate" attribute fields
	// https://tools.ietf.org/html/rfc5245#section-15.1
	var desc sdp.SessionDescription
	err := desc.Unmarshal([]byte(str))
	if err != nil {
		log.Println("Error parsing SDP: ", err.Error())
		return nil
	}
	for _, m := range desc.MediaDescriptions {
		for _, a := range m.Attributes {
			if a.IsICECandidate() {
Cecylia Bocovich's avatar
Cecylia Bocovich committed
87
				c, err := ice.UnmarshalCandidate(a.Value)
88
				if err == nil {
Cecylia Bocovich's avatar
Cecylia Bocovich committed
89
					ip := net.ParseIP(c.Address())
90
91
92
93
94
95
96
97
98
					if ip != nil && isRemoteAddress(ip) {
						return ip
					}
				}
			}
		}
	}
	// Finally look for remote IP in "c=" Connection Data field
	// https://tools.ietf.org/html/rfc4566#section-5.7
99
	for _, pattern := range remoteIPPatterns {
100
		m := pattern.FindStringSubmatch(str)
101
102
		if m != nil {
			// Ignore parsing errors, ParseIP returns nil.
103
104
105
106
107
			ip := net.ParseIP(m[1])
			if ip != nil && isRemoteAddress(ip) {
				return ip
			}

108
109
		}
	}
110

111
112
113
	return nil
}

114
115
116
117
type webRTCConn struct {
	dc *webrtc.DataChannel
	pc *webrtc.PeerConnection
	pr *io.PipeReader
118
119
120

	lock sync.Mutex // Synchronization for DataChannel destruction
	once sync.Once  // Synchronization for PeerConnection destruction
121
122
123
124
125
126
127
}

func (c *webRTCConn) Read(b []byte) (int, error) {
	return c.pr.Read(b)
}

func (c *webRTCConn) Write(b []byte) (int, error) {
128
129
	c.lock.Lock()
	defer c.lock.Unlock()
130
131
132
	if c.dc != nil {
		c.dc.Send(b)
	}
133
134
135
	return len(b), nil
}

136
137
func (c *webRTCConn) Close() (err error) {
	c.once.Do(func() {
138
		err = c.pc.Close()
139
140
	})
	return
141
142
143
144
145
146
147
}

func (c *webRTCConn) LocalAddr() net.Addr {
	return nil
}

func (c *webRTCConn) RemoteAddr() net.Addr {
148
	//Parse Remote SDP offer and extract client IP
149
	clientIP := remoteIPFromSDP(c.pc.RemoteDescription().SDP)
150
151
152
	if clientIP == nil {
		return nil
	}
Arlo Breault's avatar
Arlo Breault committed
153
	return &net.IPAddr{IP: clientIP, Zone: ""}
154
155
156
}

func (c *webRTCConn) SetDeadline(t time.Time) error {
157
	// nolint: golint
158
159
160
161
	return fmt.Errorf("SetDeadline not implemented")
}

func (c *webRTCConn) SetReadDeadline(t time.Time) error {
162
	// nolint: golint
163
164
165
166
	return fmt.Errorf("SetReadDeadline not implemented")
}

func (c *webRTCConn) SetWriteDeadline(t time.Time) error {
167
	// nolint: golint
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
	return fmt.Errorf("SetWriteDeadline not implemented")
}

func getToken() {
	<-tokens
}

func retToken() {
	tokens <- true
}

func genSessionID() string {
	buf := make([]byte, sessionIDLength)
	_, err := rand.Read(buf)
	if err != nil {
		panic(err.Error())
	}
	return strings.TrimRight(base64.StdEncoding.EncodeToString(buf), "=")
}

188
func limitedRead(r io.Reader, limit int64) ([]byte, error) {
189
	p, err := ioutil.ReadAll(&io.LimitedReader{R: r, N: limit + 1})
190
191
	if err != nil {
		return p, err
192
193
	} else if int64(len(p)) == limit+1 {
		return p[0:limit], io.ErrUnexpectedEOF
194
195
196
197
	}
	return p, err
}

198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
type SignalingServer struct {
	url                *url.URL
	transport          http.RoundTripper
	keepLocalAddresses bool
}

func (s *SignalingServer) Post(path string, payload io.Reader) ([]byte, error) {

	req, err := http.NewRequest("POST", path, payload)
	if err != nil {
		return nil, err
	}
	resp, err := s.transport.RoundTrip(req)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("remote returned status code %d", resp.StatusCode)
	}

	defer resp.Body.Close()
	return limitedRead(resp.Body, readLimit)
}

func (s *SignalingServer) pollOffer(sid string) *webrtc.SessionDescription {
	brokerPath := s.url.ResolveReference(&url.URL{Path: "proxy"})
225
	timeOfNextPoll := time.Now()
226
	for {
227
228
229
230
231
232
233
234
235
236
237
		// Sleep until we're scheduled to poll again.
		now := time.Now()
		time.Sleep(timeOfNextPoll.Sub(now))
		// Compute the next time to poll -- if it's in the past, that
		// means that the POST took longer than pollInterval, so we're
		// allowed to do another one immediately.
		timeOfNextPoll = timeOfNextPoll.Add(pollInterval)
		if timeOfNextPoll.Before(now) {
			timeOfNextPoll = now
		}

238
		body, err := messages.EncodePollRequest(sid, "standalone", currentNATType)
239
240
241
242
		if err != nil {
			log.Printf("Error encoding poll message: %s", err.Error())
			return nil
		}
243
		resp, err := s.Post(brokerPath.String(), bytes.NewBuffer(body))
244
		if err != nil {
245
246
			log.Printf("error polling broker: %s", err.Error())
		}
247

248
249
250
251
252
253
254
255
256
257
258
		offer, _, err := messages.DecodePollResponse(resp)
		if err != nil {
			log.Printf("Error reading broker response: %s", err.Error())
			log.Printf("body: %s", resp)
			return nil
		}
		if offer != "" {
			offer, err := util.DeserializeSessionDescription(offer)
			if err != nil {
				log.Printf("Error processing session description: %s", err.Error())
				return nil
259
			}
260
261
			return offer

262
263
264
265
		}
	}
}

266
267
func (s *SignalingServer) sendAnswer(sid string, pc *webrtc.PeerConnection) error {
	brokerPath := s.url.ResolveReference(&url.URL{Path: "answer"})
268
	ld := pc.LocalDescription()
269
	if !s.keepLocalAddresses {
270
271
272
273
274
		ld = &webrtc.SessionDescription{
			Type: ld.Type,
			SDP:  util.StripLocalAddresses(ld.SDP),
		}
	}
275
276
277
278
	answer, err := util.SerializeSessionDescription(ld)
	if err != nil {
		return err
	}
279
	body, err := messages.EncodeAnswerRequest(answer, sid)
280
281
282
	if err != nil {
		return err
	}
283
	resp, err := s.Post(brokerPath.String(), bytes.NewBuffer(body))
284
	if err != nil {
285
		return fmt.Errorf("error sending answer to broker: %s", err.Error())
286
	}
287

288
	success, err := messages.DecodeAnswerResponse(resp)
289
290
291
292
293
294
295
	if err != nil {
		return err
	}
	if !success {
		return fmt.Errorf("broker returned client timeout")
	}

296
297
298
	return nil
}

Arlo Breault's avatar
Arlo Breault committed
299
300
301
302
func CopyLoop(c1 io.ReadWriteCloser, c2 io.ReadWriteCloser) {
	var wg sync.WaitGroup
	copyer := func(dst io.ReadWriteCloser, src io.ReadWriteCloser) {
		defer wg.Done()
303
304
305
		// Ignore io.ErrClosedPipe because it is likely caused by the
		// termination of copyer in the other direction.
		if _, err := io.Copy(dst, src); err != nil && err != io.ErrClosedPipe {
Arlo Breault's avatar
Arlo Breault committed
306
307
308
309
310
311
312
313
314
315
316
			log.Printf("io.Copy inside CopyLoop generated an error: %v", err)
		}
		dst.Close()
		src.Close()
	}
	wg.Add(2)
	go copyer(c1, c2)
	go copyer(c2, c1)
	wg.Wait()
}

317
318
319
320
321
// We pass conn.RemoteAddr() as an additional parameter, rather than calling
// conn.RemoteAddr() inside this function, as a workaround for a hang that
// otherwise occurs inside of conn.pc.RemoteDescription() (called by
// RemoteAddr). https://bugs.torproject.org/18628#comment:8
func datachannelHandler(conn *webRTCConn, remoteAddr net.Addr) {
322
323
324
	defer conn.Close()
	defer retToken()

325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
	u, err := url.Parse(relayURL)
	if err != nil {
		log.Fatalf("invalid relay url: %s", err)
	}

	if remoteAddr != nil {
		// Encode client IP address in relay URL
		q := u.Query()
		clientIP := remoteAddr.String()
		q.Set("client_ip", clientIP)
		u.RawQuery = q.Encode()
	} else {
		log.Printf("no remote address given in websocket")
	}

340
	ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
341
	if err != nil {
David Fifield's avatar
David Fifield committed
342
		log.Printf("error dialing relay: %s", err)
343
344
		return
	}
345
	wsConn := websocketconn.New(ws)
346
347
	log.Printf("connected to relay")
	defer wsConn.Close()
348
	CopyLoop(conn, wsConn)
349
350
351
352
353
354
355
	log.Printf("datachannelHandler ends")
}

// Create a PeerConnection from an SDP offer. Blocks until the gathering of ICE
// candidates is complete and the answer is available in LocalDescription.
// Installs an OnDataChannel callback that creates a webRTCConn and passes it to
// datachannelHandler.
356
func makePeerConnectionFromOffer(sdp *webrtc.SessionDescription,
357
358
359
360
	config webrtc.Configuration,
	dataChan chan struct{},
	handler func(conn *webRTCConn, remoteAddr net.Addr)) (*webrtc.PeerConnection, error) {

361
362
363
364
	pc, err := webrtc.NewPeerConnection(config)
	if err != nil {
		return nil, fmt.Errorf("accept: NewPeerConnection: %s", err)
	}
365
366
367
	pc.OnDataChannel(func(dc *webrtc.DataChannel) {
		log.Println("OnDataChannel")
		close(dataChan)
368

369
370
		pr, pw := io.Pipe()
		conn := &webRTCConn{pc: pc, dc: dc, pr: pr}
371

372
		dc.OnOpen(func() {
373
			log.Println("OnOpen channel")
374
375
		})
		dc.OnClose(func() {
376
377
378
379
			conn.lock.Lock()
			defer conn.lock.Unlock()
			log.Println("OnClose channel")
			conn.dc = nil
380
			dc.Close()
381
			pw.Close()
382
		})
383
384
385
386
387
388
		dc.OnMessage(func(msg webrtc.DataChannelMessage) {
			var n int
			n, err = pw.Write(msg.Data)
			if err != nil {
				if inerr := pw.CloseWithError(err); inerr != nil {
					log.Printf("close with error generated an error: %v", inerr)
389
390
				}
			}
391
392
393
394
395
396
397
			if n != len(msg.Data) {
				panic("short write")
			}
		})

		go handler(conn, conn.RemoteAddr())
	})
398
399
400
401
	// As of v3.0.0, pion-webrtc uses trickle ICE by default.
	// We have to wait for candidate gathering to complete
	// before we send the offer
	done := webrtc.GatheringCompletePromise(pc)
402
403
404
405
	err = pc.SetRemoteDescription(*sdp)
	if err != nil {
		if inerr := pc.Close(); inerr != nil {
			log.Printf("unable to call pc.Close after pc.SetRemoteDescription with error: %v", inerr)
406
		}
407
408
409
		return nil, fmt.Errorf("accept: SetRemoteDescription: %s", err)
	}
	log.Println("sdp offer successfully received.")
410

411
412
413
414
415
416
417
418
	log.Println("Generating answer...")
	answer, err := pc.CreateAnswer(nil)
	// blocks on ICE gathering. we need to add a timeout if needed
	// not putting this in a separate go routine, because we need
	// SetLocalDescription(answer) to be called before sendAnswer
	if err != nil {
		if inerr := pc.Close(); inerr != nil {
			log.Printf("ICE gathering has generated an error when calling pc.Close: %v", inerr)
419
		}
420
421
		return nil, err
	}
422

423
424
425
426
	err = pc.SetLocalDescription(answer)
	if err != nil {
		if err = pc.Close(); err != nil {
			log.Printf("pc.Close after setting local description returned : %v", err)
427
		}
428
		return nil, err
429
	}
430
431
	// Wait for ICE candidate gathering to complete
	<-done
432
433
434
	return pc, nil
}

435
436
437
438
439
440
441
442
443
444
// Create a new PeerConnection. Blocks until the gathering of ICE
// candidates is complete and the answer is available in LocalDescription.
func makeNewPeerConnection(config webrtc.Configuration,
	dataChan chan struct{}) (*webrtc.PeerConnection, error) {

	pc, err := webrtc.NewPeerConnection(config)
	if err != nil {
		return nil, fmt.Errorf("accept: NewPeerConnection: %s", err)
	}

445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
	// Must create a data channel before creating an offer
	// https://github.com/pion/webrtc/wiki/Release-WebRTC@v3.0.0
	dc, err := pc.CreateDataChannel("test", &webrtc.DataChannelInit{})
	if err != nil {
		log.Printf("CreateDataChannel ERROR: %s", err)
		return nil, err
	}
	dc.OnOpen(func() {
		log.Println("WebRTC: DataChannel.OnOpen")
		close(dataChan)
	})
	dc.OnClose(func() {
		log.Println("WebRTC: DataChannel.OnClose")
		dc.Close()
	})

461
462
463
464
465
466
467
468
	offer, err := pc.CreateOffer(nil)
	// TODO: Potentially timeout and retry if ICE isn't working.
	if err != nil {
		log.Println("Failed to prepare offer", err)
		pc.Close()
		return nil, err
	}
	log.Println("WebRTC: Created offer")
469
470
471
472
473

	// As of v3.0.0, pion-webrtc uses trickle ICE by default.
	// We have to wait for candidate gathering to complete
	// before we send the offer
	done := webrtc.GatheringCompletePromise(pc)
474
475
476
477
478
479
480
481
	err = pc.SetLocalDescription(offer)
	if err != nil {
		log.Println("Failed to prepare offer", err)
		pc.Close()
		return nil, err
	}
	log.Println("WebRTC: Set local description")

482
483
	// Wait for ICE candidate gathering to complete
	<-done
484
485
486
	return pc, nil
}

487
func runSession(sid string) {
488
	offer := broker.pollOffer(sid)
489
490
491
492
493
	if offer == nil {
		log.Printf("bad offer from broker")
		retToken()
		return
	}
494
	dataChan := make(chan struct{})
495
	pc, err := makePeerConnectionFromOffer(offer, config, dataChan, datachannelHandler)
496
	if err != nil {
David Fifield's avatar
David Fifield committed
497
		log.Printf("error making WebRTC connection: %s", err)
498
499
500
		retToken()
		return
	}
501
	err = broker.sendAnswer(sid, pc)
502
	if err != nil {
David Fifield's avatar
David Fifield committed
503
		log.Printf("error sending answer to client through broker: %s", err)
504
505
506
		if inerr := pc.Close(); inerr != nil {
			log.Printf("error calling pc.Close: %v", inerr)
		}
507
508
509
		retToken()
		return
	}
510
511
512
513
514
515
516
517
	// Set a timeout on peerconnection. If the connection state has not
	// advanced to PeerConnectionStateConnected in this time,
	// destroy the peer connection and return the token.
	select {
	case <-dataChan:
		log.Println("Connection successful.")
	case <-time.After(dataChannelTimeout):
		log.Println("Timed out waiting for client to open data channel.")
518
519
520
		if err := pc.Close(); err != nil {
			log.Printf("error calling pc.Close: %v", err)
		}
521
522
		retToken()
	}
523
524
525
}

func main() {
526
527
	var capacity uint
	var stunURL string
David Fifield's avatar
David Fifield committed
528
	var logFilename string
529
	var rawBrokerURL string
Arlo Breault's avatar
Arlo Breault committed
530
	var unsafeLogging bool
531
	var keepLocalAddresses bool
532
533

	flag.UintVar(&capacity, "capacity", 10, "maximum concurrent clients")
534
	flag.StringVar(&rawBrokerURL, "broker", defaultBrokerURL, "broker URL")
535
536
	flag.StringVar(&relayURL, "relay", defaultRelayURL, "websocket relay URL")
	flag.StringVar(&stunURL, "stun", defaultSTUNURL, "stun URL")
David Fifield's avatar
David Fifield committed
537
	flag.StringVar(&logFilename, "log", "", "log filename")
Arlo Breault's avatar
Arlo Breault committed
538
	flag.BoolVar(&unsafeLogging, "unsafe-logging", false, "prevent logs from being scrubbed")
539
	flag.BoolVar(&keepLocalAddresses, "keep-local-addresses", false, "keep local LAN address ICE candidates")
540
541
	flag.Parse()

542
	var logOutput io.Writer = os.Stderr
David Fifield's avatar
David Fifield committed
543
	log.SetFlags(log.LstdFlags | log.LUTC)
David Fifield's avatar
David Fifield committed
544
	if logFilename != "" {
David Fifield's avatar
fmt    
David Fifield committed
545
		f, err := os.OpenFile(logFilename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
David Fifield's avatar
David Fifield committed
546
547
548
549
		if err != nil {
			log.Fatal(err)
		}
		defer f.Close()
550
		logOutput = io.MultiWriter(os.Stderr, f)
David Fifield's avatar
David Fifield committed
551
	}
Arlo Breault's avatar
Arlo Breault committed
552
553
554
555
556
557
	if unsafeLogging {
		log.SetOutput(logOutput)
	} else {
		// We want to send the log output through our scrubber first
		log.SetOutput(&safelog.LogScrubber{Output: logOutput})
	}
David Fifield's avatar
David Fifield committed
558

559
560
	log.Println("starting")

561
	var err error
562
	broker = new(SignalingServer)
563
	broker.keepLocalAddresses = keepLocalAddresses
564
	broker.url, err = url.Parse(rawBrokerURL)
565
566
567
	if err != nil {
		log.Fatalf("invalid broker url: %s", err)
	}
568
	_, err = url.Parse(stunURL)
569
570
571
	if err != nil {
		log.Fatalf("invalid stun url: %s", err)
	}
572
	_, err = url.Parse(relayURL)
573
574
575
576
	if err != nil {
		log.Fatalf("invalid relay url: %s", err)
	}

577
	broker.transport = http.DefaultTransport.(*http.Transport)
578
	broker.transport.(*http.Transport).ResponseHeaderTimeout = 15 * time.Second
579
580
581
582
583
584
585
	config = webrtc.Configuration{
		ICEServers: []webrtc.ICEServer{
			{
				URLs: []string{stunURL},
			},
		},
	}
586
587
	tokens = make(chan bool, capacity)
	for i := uint(0); i < capacity; i++ {
588
589
590
		tokens <- true
	}

591
592
	// use probetest to determine NAT compatability
	checkNATType(config, defaultProbeURL)
593
594
	log.Printf("NAT type: %s", currentNATType)

595
596
597
598
599
600
	for {
		getToken()
		sessionID := genSessionID()
		runSession(sessionID)
	}
}
601

602
func checkNATType(config webrtc.Configuration, probeURL string) {
603
604

	var err error
605
606
607
608
609
610
611

	probe := new(SignalingServer)
	probe.transport = http.DefaultTransport.(*http.Transport)
	probe.transport.(*http.Transport).ResponseHeaderTimeout = 30 * time.Second
	probe.url, err = url.Parse(probeURL)
	if err != nil {
		log.Printf("Error parsing url: %s", err.Error())
612
	}
613
614
615
616

	// create offer
	dataChan := make(chan struct{})
	pc, err := makeNewPeerConnection(config, dataChan)
617
	if err != nil {
618
619
620
621
622
623
		log.Printf("error making WebRTC connection: %s", err)
		return
	}

	offer := pc.LocalDescription()
	sdp, err := util.SerializeSessionDescription(offer)
624
	log.Printf("Offer: %s", sdp)
625
626
627
	if err != nil {
		log.Printf("Error encoding probe message: %s", err.Error())
		return
628
	}
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667

	// send offer
	body, err := messages.EncodePollResponse(sdp, true, "")
	if err != nil {
		log.Printf("Error encoding probe message: %s", err.Error())
		return
	}
	resp, err := probe.Post(probe.url.String(), bytes.NewBuffer(body))
	if err != nil {
		log.Printf("error polling probe: %s", err.Error())
		return
	}

	sdp, _, err = messages.DecodeAnswerRequest(resp)
	if err != nil {
		log.Printf("Error reading probe response: %s", err.Error())
		return
	}
	answer, err := util.DeserializeSessionDescription(sdp)
	if err != nil {
		log.Printf("Error setting answer: %s", err.Error())
		return
	}
	err = pc.SetRemoteDescription(*answer)
	if err != nil {
		log.Printf("Error setting answer: %s", err.Error())
		return
	}

	select {
	case <-dataChan:
		currentNATType = NATUnrestricted
	case <-time.After(dataChannelTimeout):
		currentNATType = NATRestricted
	}
	if err := pc.Close(); err != nil {
		log.Printf("error calling pc.Close: %v", err)
	}

668
}