forked from pixelbender/go-stun
/
client.go
104 lines (95 loc) · 2.21 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package stun
import (
"bytes"
"crypto/rand"
"net"
"time"
)
var retransmissionTimeout = 500 * time.Millisecond
var defaultTimeout = 39500 * time.Millisecond
// Client is a STUN client.
type Client struct {
*Conn
Timeout time.Duration
}
func NewClient(c net.Conn, config *Config) *Client {
return &Client{
Conn: NewConn(c, config),
Timeout: defaultTimeout,
}
}
// RoundTrip sends STUN request and waits for a STUN response within the transaction.
// Retransmits on unrelied over transport protocol connection.
// Tries to authorize
// No STUN redirect is supported.
// Returns STUN error responses as errors.
func (c *Client) RoundTrip(req *Message) (res *Message, err error) {
if err = c.WriteMessage(req); err != nil {
return
}
rto := retransmissionTimeout
deadline := time.Now().Add(c.Timeout)
if !c.reliable {
c.SetReadDeadline(time.Now().Add(rto))
} else if c.Timeout > 0 {
c.SetReadDeadline(deadline)
}
for {
res, err = c.ReadMessage()
if err != nil {
if ne, ok := err.(net.Error); ok && ne.Temporary() && !c.reliable && time.Now().Before(deadline) {
// Retransmit
if err = c.WriteMessage(req); err != nil {
return
}
rto *= 2
c.SetReadDeadline(time.Now().Add(rto))
continue
}
return
}
if !bytes.Equal(req.Transaction, res.Transaction) {
if c.reliable {
return nil, ErrFormat
}
continue
}
if attr, ok := res.Attributes[AttrErrorCode]; ok {
code := attr.(*Error)
if code.Code == CodeUnauthorized && req.Key == nil {
// TODO: store nonce
req.Attributes[AttrRealm] = res.Attributes[AttrRealm]
req.Attributes[AttrNonce] = res.Attributes[AttrNonce]
req.Key, err = c.config.getAuthKey(req.Attributes)
if err != nil {
return
}
if req.Key != nil {
// New transaction
rand.Read(req.Transaction[4:])
c.key = req.Key
if err = c.WriteMessage(req); err != nil {
return
}
continue
}
}
// TODO: handle alternate server
}
return res, nil
}
}
func GetServerAddress(h string, secure bool) string {
host, port, err := net.SplitHostPort(h)
if err != nil {
host = h
}
if port == "" {
if secure {
port = "5478"
} else {
port = "3478"
}
}
return net.JoinHostPort(host, port)
}