1. 协议及相关代码
stun协议说明:datatracker.ietf.org/doc/html/rfc3489
stun协议扩展:datatracker.ietf.org/doc/html/rfc5389
turn协议说明:datatracker.ietf.org/doc/html/rfc5766
c实现:github.com/coturn/coturn
go实现:github.com/pion
2.turn协议概览
turn协议是stun协议的扩展
stun消息头
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0 0| STUN Message Type | Message Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Magic Cookie |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
| Transaction ID (96 bits) |
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
stun message type:定义了消息的class及method
message length:定义了消息的长度,不包含20字节的消息头
magic cookie:固定值,stun协议的标识
transaction id:事务id
stun消息体
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Value (variable) ....
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type:属性名
length:value长度
value:属性值,需注意,如果属性值没有填充满一行(4字节),则需要填充满,即对齐
整体协议的函数定义
// Message represents a single STUN packet. It uses aggressive internal
// buffering to enable zero-allocation encoding and decoding,
// so there are some usage constraints:
//
// Message, its fields, results of m.Get or any attribute a.GetFrom
// are valid only until Message.Raw is not modified.
type Message struct {
Type MessageType
Length uint32 // len(Raw) not including header
TransactionID [TransactionIDSize]byte
Attributes Attributes
Raw []byte
}
// MessageType is STUN Message Type Field.
type MessageType struct {
Method Method // e.g. binding
Class MessageClass // e.g. request
}
stun消息头的信息
// 0 1
// 2 3 4 5 6 7 8 9 0 1 2 3 4 5
// +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
// |M |M |M|M|M|C|M|M|M|C|M|M|M|M|
// |11|10|9|8|7|1|6|5|4|0|3|2|1|0|
// +--+--+-+-+-+-+-+-+-+-+-+-+-+-+
协议中的C位,C1C0表示stun消息类
// MessageClass is 8-bit representation of 2-bit class of STUN Message Class.
type MessageClass byte
// Possible values for message class in STUN Message Type.
const (
ClassRequest MessageClass = 0x00 // 0b00
ClassIndication MessageClass = 0x01 // 0b01
ClassSuccessResponse MessageClass = 0x02 // 0b10
ClassErrorResponse MessageClass = 0x03 // 0b11
)
class和method读取go实现方式
// ReadValue decodes uint16 into MessageType.
func (t *MessageType) ReadValue(v uint16) {
// Decoding class.
// We are taking first bit from v >> 4 and second from v >> 7.
c0 := (v >> classC0Shift) & c0Bit
c1 := (v >> classC1Shift) & c1Bit
class := c0 + c1
t.Class = MessageClass(class)
// Decoding method.
a := v & methodABits // A(M0-M3)
b := (v >> methodBShift) & methodBBits // B(M4-M6)
d := (v >> methodDShift) & methodDBits // D(M7-M11)
m := a + b + d
t.Method = Method(m)
}
method定义如下:
// Method is uint16 representation of 12-bit STUN method.
type Method uint16
// Possible methods for STUN Message.
const (
MethodBinding Method = 0x001
MethodAllocate Method = 0x003
MethodRefresh Method = 0x004
MethodSend Method = 0x006
MethodData Method = 0x007
MethodCreatePermission Method = 0x008
MethodChannelBind Method = 0x009
)
// Methods from RFC 6062.
const (
MethodConnect Method = 0x000a
MethodConnectionBind Method = 0x000b
MethodConnectionAttempt Method = 0x000c
)
还有拓展的协议,可自行查阅
目前turnserver使用的class和method关系
服务端协议处理
func getMessageHandler(class stun.MessageClass, method stun.Method) (func(r Request, m *stun.Message) error, error) {
switch class {
case stun.ClassIndication:
switch method {
case stun.MethodSend:
return handleSendIndication, nil
default:
return nil, fmt.Errorf("%w: %s", errUnexpectedMethod, method)
}
case stun.ClassRequest:
switch method {
case stun.MethodAllocate:
return handleAllocateRequest, nil
case stun.MethodRefresh:
return handleRefreshRequest, nil
case stun.MethodCreatePermission:
return handleCreatePermissionRequest, nil
case stun.MethodChannelBind:
return handleChannelBindRequest, nil
case stun.MethodBinding:
return handleBindingRequest, nil
default:
return nil, fmt.Errorf("%w: %s", errUnexpectedMethod, method)
}
default:
return nil, fmt.Errorf("%w: %s", errUnexpectedClass, class)
}
}
客户端侧协议处理
if msg.Type.Class == stun.ClassIndication {
switch msg.Type.Method {
case stun.MethodData:
var peerAddr proto.PeerAddress
if err := peerAddr.GetFrom(msg); err != nil {
return err
}
from = &net.UDPAddr{
IP: peerAddr.IP,
Port: peerAddr.Port,
}
var data proto.Data
if err := data.GetFrom(msg); err != nil {
return err
}
c.log.Tracef("data indication received from %s", from.String())
relayedConn := c.relayedUDPConn()
if relayedConn == nil {
c.log.Debug("no relayed conn allocated")
return nil // Silently discard
}
relayedConn.HandleInbound(data, from)
替代stun协议传输的channeldata协议(用于传输音视频数据)
stun协议header固定20字节 vs channeldata协议header固定4字节
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Channel Number | Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
/ Application Data /
/ /
| |
| +-------------------------------+
| |
+-------------------------------+
3.安全性
没有认证的访问是不安全,支持的安全认证方式:
1)无认证(允许匿名访问),在配置文件中的选项为no-auth,开启这一选项,即使有一个user选项开启了(在配置文件或者命令行或者usersdb文件中),no-auth也会被启用;但如果no-auth,user,lt-cred-mech都没有开启,默认是开启no-auth。
2)长期凭据机制,在配置文件中的选项为lt-cred-mech,如果no-auth和lt-cred-mech都未开启,但有一个user选项开启了(在配置文件或者命令行或者usersdb文件中),lt-cred-mech也会被自动开启。
3)时间有限的长期凭据,在配置文件中的选项为use-auth-secret,需要实现TURN REST API使用认证秘密,可以在数据库的turn_secret table里查询,这是动态认证秘密,turn_secret table里的密钥不固定,可以修改。
4)静态认证密钥,在配置文件中的选项为static-auth-secret,需要实现TURN REST API使用认证秘密,这是3)的简单情形,即指定一个固定不变的密钥
基于静态认证用户名密码生成方案:
具体可查看 github.com/coturn/coturn/wiki/turnserver#turn-rest-api
temporary-username = "timestamp" + ":" + "username"
temporary-password = base64_encode(hmac-sha1(input=temporary-username, key=shared-secret))