searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

中继服务协议详解

2023-10-24 09:58:57
46
0

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))

0条评论
作者已关闭评论
郑****辉
6文章数
0粉丝数
郑****辉
6 文章 | 0 粉丝
郑****辉
6文章数
0粉丝数
郑****辉
6 文章 | 0 粉丝
原创

中继服务协议详解

2023-10-24 09:58:57
46
0

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))

文章来自个人专栏
中继服务
2 文章 | 1 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0