Skip to content

Commit dfae478

Browse files
committed
Add STUN server
1 parent 8587e88 commit dfae478

File tree

11 files changed

+398
-132
lines changed

11 files changed

+398
-132
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2016 Vasily Vasilyev
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
[![Build Status](https://travis-ci.org/pixelbender/go-stun.svg)](https://travis-ci.org/pixelbender/go-stun)
44
[![Coverage Status](https://coveralls.io/repos/github/pixelbender/go-stun/badge.svg?branch=master)](https://coveralls.io/github/pixelbender/go-stun?branch=master)
5+
[![Go Report Card](https://goreportcard.com/badge/github.com/pixelbender/go-stun)](https://goreportcard.com/report/github.com/pixelbender/go-stun)
56
[![GoDoc](https://godoc.org/github.com/pixelbender/go-stun?status.svg)](https://godoc.org/github.com/pixelbender/go-stun)
67

78
There is no working release yet. Stay tuned.
89

910
## Roadmap
1011

11-
- [ ] STUN Message Encoder/Decoder
12+
- [x] STUN Message Encoder/Decoder
1213
- [ ] STUN Server
1314
- [ ] STUN Message integrity check
1415
- [ ] ICE STUN Attributes

stun/attributes.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const (
4040
// Attribute is the interface that represents a STUN message attribute.
4141
type Attribute interface {
4242
// Encode writes the attribute to the byte array.
43+
// Returns io.ErrUnexpectedEOF error if the byte array length is not enough.
4344
Encode(b []byte) (int, error)
4445
}
4546

@@ -78,7 +79,7 @@ func (attr ChangeRequest) Encode(b []byte) (int, error) {
7879
// UnknownAttributes represents the UNKNOWN-ATTRIBUTES attribute
7980
type UnknownAttributes []uint16
8081

81-
// Encode writes the attribute to the byte array.
82+
// Encode writes the attribute to the byte array.F
8283
func (attr UnknownAttributes) Encode(b []byte) (int, error) {
8384
n := len(attr) << 1
8485
if len(b) < n {

stun/conn.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,49 @@
11
package stun
22

3-
import "net"
3+
import (
4+
"bytes"
5+
"fmt"
6+
"net"
7+
)
48

59
// A Conn represents the STUN agent and implements the STUN protocol over net.Conn interface.
610
type Conn struct {
711
net.Conn
12+
enc *Encoder
13+
dec *Decoder
814
}
915

1016
// NewConn creates a Conn connection on the given net.Conn
1117
func NewConn(inner net.Conn) *Conn {
12-
return &Conn{inner}
18+
return &Conn{
19+
inner,
20+
NewEncoder(inner),
21+
NewDecoder(inner),
22+
}
23+
}
24+
25+
// ReadMessage reads STUN messages from the connection.
26+
func (conn *Conn) ReadMessage() (*Message, error) {
27+
return conn.dec.Decode()
28+
}
29+
30+
// WriteMessage writes STUN messages to the connection.
31+
func (conn *Conn) WriteMessage(msg *Message) error {
32+
return conn.enc.Encode(msg)
33+
}
34+
35+
// Exchange sends STUN request and returns STUN response or error.
36+
func (conn *Conn) Exchange(req *Message) (*Message, error) {
37+
err := conn.WriteMessage(req)
38+
if err != nil {
39+
return nil, err
40+
}
41+
res, err := conn.ReadMessage()
42+
if err != nil {
43+
return nil, err
44+
}
45+
if !bytes.Equal(req.Transaction, res.Transaction) {
46+
return nil, fmt.Errorf("stun: transaction error")
47+
}
48+
return res, nil
1349
}

stun/decoder.go

Lines changed: 23 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,37 @@
11
package stun
22

33
import (
4-
"errors"
4+
"bufio"
55
"io"
66
)
77

8-
// ErrWrongFormat is the error returned by ReadMessage when data format is wrong.
9-
var ErrWrongFormat = errors.New("stun: wrong message format")
10-
11-
// A Decoder reads and decodes STUN messages from the byte array.
8+
// A Decoder reads and decodes STUN message from an input stream.
129
type Decoder struct {
10+
buf *bufio.Reader
1311
}
1412

15-
// ReadMessage reads STUN messages from the byte array.
16-
func (dec *Decoder) ReadMessage(b []byte) (*Message, error) {
17-
if len(b) < 20 {
18-
return nil, io.EOF
13+
// NewDecoder returns a new decoder that reads from r.
14+
func NewDecoder(r io.Reader) *Decoder {
15+
if buf, ok := r.(*bufio.Reader); ok {
16+
return &Decoder{buf}
1917
}
20-
n, p := getInt16(b[2:]), b[20:]
21-
if len(p) < n {
22-
return nil, io.EOF
18+
return &Decoder{bufio.NewReaderSize(r, bufferSize)}
19+
}
20+
21+
// Decode reads STUN message from the stream.
22+
func (dec *Decoder) Decode() (*Message, error) {
23+
b, err := dec.buf.Peek(20)
24+
if err != nil {
25+
return nil, err
2326
}
24-
msg := &Message{
25-
Type: getUint16(b),
26-
Cookie: getUint32(b[4:]),
27-
Attributes: make(map[uint16]Attribute),
27+
n := getInt16(b[2:]) + 20
28+
if b, err = dec.buf.Peek(n); err != nil {
29+
return nil, err
2830
}
29-
copy(msg.Transaction[:], b[8:20])
30-
for len(p) > 4 {
31-
at, an := getUint16(p), getInt16(p[2:])
32-
m := an
33-
if mod := n & 3; mod != 0 {
34-
m += 4 - mod
35-
}
36-
if p = p[4:]; len(p) < m {
37-
return nil, ErrWrongFormat
38-
}
39-
msg.Attributes[at], p = RawAttribute(p[:an]), p[an:]
31+
msg, err := ReadMessage(b)
32+
if err != nil {
33+
return nil, err
4034
}
41-
42-
// TODO: check message integrity + fingerprint
43-
44-
return msg, nil
45-
}
46-
47-
func getInt16(b []byte) int {
48-
return int(b[1]) | int(b[0])<<8
49-
}
50-
51-
func getUint16(b []byte) uint16 {
52-
return uint16(b[1]) | uint16(b[0])<<8
53-
}
54-
55-
func getUint32(b []byte) uint32 {
56-
return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
35+
dec.buf.Discard(n)
36+
return msg, err
5737
}

stun/encoder.go

Lines changed: 14 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,32 @@
11
package stun
22

33
import (
4-
"fmt"
54
"io"
65
)
76

8-
var defaultBufferSize = 1024
9-
10-
// An Encoder writes and encodes STUN messages to the byte array.
7+
// An Encoder encodes and writes STUN message to an output stream.
118
type Encoder struct {
9+
w io.Writer
1210
buf []byte
13-
pos int
14-
}
15-
16-
// Reset resets the encoder buffer to be empty, but it retains the underlying storage for use by future writes.
17-
func (enc *Encoder) Reset() {
18-
enc.pos = 0
1911
}
2012

21-
// Bytes returns the byte array representation of the encoded messages.
22-
func (enc *Encoder) Bytes() []byte {
23-
return enc.buf[:enc.pos]
13+
// NewEncoder returns a new encoder that writes to w.
14+
func NewEncoder(w io.Writer) *Encoder {
15+
return &Encoder{w: w}
2416
}
2517

26-
// WriteMessage writes STUN message to the byte array.
27-
func (enc *Encoder) WriteMessage(msg *Message) error {
18+
// Encode writes STUN message to the stream.
19+
func (enc *Encoder) Encode(msg *Message) error {
2820
if enc.buf == nil {
29-
enc.buf = make([]byte, 0, defaultBufferSize)
21+
enc.buf = make([]byte, bufferSize)
3022
}
31-
b := enc.buf[enc.pos:]
32-
if len(b) < 20 {
33-
return io.ErrUnexpectedEOF
23+
n, err := msg.Encode(enc.buf)
24+
if err != nil {
25+
return err
3426
}
35-
putUint16(b, msg.Type)
36-
putUint32(b[4:], msg.Cookie)
37-
copy(b[8:], msg.Transaction[:])
38-
p := b[20:]
39-
40-
for at, attr := range msg.Attributes {
41-
if attr == nil {
42-
return fmt.Errorf("stun: empty attribute 0x%x", at)
43-
}
44-
if len(p) < 4 {
45-
return io.ErrUnexpectedEOF
46-
}
47-
a := p[4:]
48-
n, err := attr.Encode(a)
49-
if err != nil {
50-
return err
51-
}
52-
if n < 0 || len(a) < n {
53-
return fmt.Errorf("stun: attribute encoding error 0x%x", at)
54-
}
55-
putUint16(p, at)
56-
putInt16(p[2:], n)
57-
pad := n
58-
if mod := n & 3; mod != 0 {
59-
pad += 4 - mod
60-
}
61-
if len(a) < pad {
62-
return io.ErrUnexpectedEOF
63-
}
64-
for i := n; i < pad; i++ {
65-
a[i] = 0
66-
}
67-
p = a[n:]
27+
_, err = enc.w.Write(enc.buf[:n])
28+
if err != nil {
29+
return err
6830
}
69-
70-
// TODO: create message integrity + fingerprint
71-
7231
return nil
7332
}
74-
75-
func putInt16(b []byte, v int) {
76-
b[0] = byte(v >> 8)
77-
b[1] = byte(v)
78-
}
79-
80-
func putUint16(b []byte, v uint16) {
81-
b[0] = byte(v >> 8)
82-
b[1] = byte(v)
83-
}
84-
85-
func putUint32(b []byte, v uint32) {
86-
b[0] = byte(v >> 24)
87-
b[1] = byte(v >> 16)
88-
b[2] = byte(v >> 8)
89-
b[3] = byte(v)
90-
}

0 commit comments

Comments
 (0)