1. 点播的配置
假设配置文件 nginx.conf 中对 rtmp 配置如下:
# 创建的子进程数
worker_processes 1;
#error_log logs/error.log;
error_log stderr debug;
#error_log logs/error.log info;
#pid logs/nginx.pid;
#关闭以守护进程方式运行,方便进行调试
daemon off;
master_process on;
events {
worker_connections 1024;
}
rtmp {
server {
listen 1935; # rtmp传输端口
chunk_size 4096; # 数据传输块大小
application vod {
play /home/xxx/samba/nginx/vod; # 视频文件存放位置
}
}
}
2. handshake 过程
2.1 ngx_rtmp_init_connection
void ngx_rtmp_init_connection(ngx_connection_t *c)
{
ngx_uint_t i;
ngx_rtmp_port_t *port;
struct sockaddr *sa;
struct sockaddr_in *sin;
ngx_rtmp_in_addr_t *addr;
ngx_rtmp_session_t *s;
ngx_rtmp_addr_conf_t *addr_conf;
ngx_int_t unix_socket;
#if (NGX_HAVE_INET6)
struct sockaddr_in6 *sin6;
ngx_rtmp_in6_addr_t *addr6;
#endif
/* rtmp 连接的计数值加 1 */
++ngx_rtmp_naccepted;
/* find the server configuration for the address:port */
/* AF_INET only */
port = c->listening->servers;
unix_socket = 0;
if (port->naddrs > 1) {
/*
* There are several addresses on this port and one of them
* is the "*:port" wildcard so getsockname() is needed to determine
* the server address.
*
* AcceptEx() already gave this address.
*/
if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
ngx_rtmp_close_connection(c);
return;
}
sa = c->local_sockaddr;
switch (sa->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
sin6 = (struct sockaddr_in6 *) sa;
addr6 = port->addrs;
/* the last address is "*" */
for (i = 0; i < port->naddrs - 1; i++) {
if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {
break;
}
}
addr_conf = &addr6[i].conf;
break;
#endif
case AF_UNIX:
unix_socket = 1;
/* fall through */
default: /* AF_INET */
sin = (struct sockaddr_in *) sa;
addr = port->addrs;
/* the last address is "*" */
for (i = 0; i < port->naddrs - 1; i++) {
if (addr[i].addr == sin->sin_addr.s_addr) {
break;
}
}
addr_conf = &addr[i].conf;
break;
}
} else {
switch (c->local_sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
case AF_INET6:
addr6 = port->addrs;
addr_conf = &addr6[0].conf;
break;
#endif
case AF_UNIX:
unix_socket = 1;
/* fall through */
default: /* AF_INET */
addr = port->addrs;
addr_conf = &addr[0].conf;
break;
}
}
ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%ui client connected '%V'",
c->number, &c->addr_text);
/* 创建并初始化一个 rtmp 会话 */
s = ngx_rtmp_init_session(c, addr_conf);
if (s == NULL) {
return;
}
/* only auto-pushed connections are
* done through unix socket */
s->auto_pushed = unix_socket;
/* 检测是否是开启了代理服务 */
if (addr_conf->proxy_protocol) {
ngx_rtmp_proxy_protocol(s);
} else {
/* 开始 rtmp 的握手过程 */
ngx_rtmp_handshake(s);
}
}
2.2 ngx_rtmp_init_session
ngx_rtmp_session_t *ngx_rtmp_init_session(ngx_connection_t *c, ngx_rtmp_addr_conf_t *addr_conf)
{
ngx_rtmp_session_t *s;
ngx_rtmp_core_srv_conf_t *cscf;
ngx_rtmp_error_log_ctx_t *ctx;
/* 为该会话分配内存 */
s = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_session_t) +
sizeof(ngx_chain_t *) * ((ngx_rtmp_core_srv_conf_t *)
addr_conf->ctx-> srv_conf[ngx_rtmp_core_module
.ctx_index])->out_queue);
if (s == NULL) {
ngx_rtmp_close_connection(c);
return NULL;
}
/* 指向 server{} 下所属的 ngx_rtmp_conf_ctx_t 下的 main_conf 指针数组 */
s->main_conf = addr_conf->ctx->main_conf;
/* 指向 server{} 下所属的 ngx_rtmp_conf_ctx_t 下的 srv_conf 指针数组 */
s->srv_conf = addr_conf->ctx->srv_conf;
s->addr_text = &addr_conf->addr_text;
/* 将 c->data 指向该会话结构体首地址 */
c->data = s;
s->connection = c;
ctx = ngx_palloc(c->pool, sizeof(ngx_rtmp_error_log_ctx_t));
if (ctx == NULL) {
ngx_rtmp_close_connection(c);
return NULL;
}
ctx->client = &c->addr_text;
ctx->session = s;
c->log->connection = c->number;
c->log->handler = ngx_rtmp_log_error;
c->log->data = ctx;
c->log->action = NULL;
c->log_error = NGX_ERROR_INFO;
s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_rtmp_max_module);
if (s->ctx == NULL) {
ngx_rtmp_close_connection(c);
return NULL;
}
/* 获取 server{} 下创建的 ngx_rtmp_core_module 模块创建的
* ngx_rtmp_core_srv_conf_t 配置项结构体,该结构体也对应着
* 当前这个server{},因此也就保存着当前 server{} 下所有的
* 配置项 */
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
/* 输出队列的个数 */
s->out_queue = cscf->out_queue;
s->out_cork = cscf->out_cork;
s->in_streams = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_stream_t)
* cscf->max_streams);
if (s->in_streams == NULL) {
ngx_rtmp_close_connection(c);
return NULL;
}
#if (nginx_version >= 1007005)
ngx_queue_init(&s->posted_dry_events);
#endif
s->epoch = ngx_current_msec;
s->timeout = cscf->timeout;
s->buflen = cscf->buflen;
/* 设置 */
ngx_rtmp_set_chunk_size(s, NGX_RTMP_DEFAULT_CHUNK_SIZE);
/* 遍历 events 数组中保存的每个 RTMP 模块的 NGX_RTMP_CONNECT 事件,
* 这里仅有 TMP_LIMIT_MODULE 设置了该事件 */
if (ngx_rtmp_fire_event(s, NGX_RTMP_CONNECT, NULL, NULL) != NGX_OK) {
ngx_rtmp_finalize_session(s);
return NULL;
}
return s;
}
2.2.1 ngx_rtmp_set_chunk_size
ngx_int_t ngx_rtmp_set_chunk_size(ngx_rtmp_session_t *s, ngx_uint_t size)
{
ngx_rtmp_core_srv_conf_t *cscf;
ngx_chain_t *li, *fli, *lo, *flo;
ngx_buf_t *bi, *bo;
ngx_int_t n;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"setting chunk_size=%ui", size);
if (size > NGX_RTMP_MAX_CHUNK_SIZE) {
ngx_log_error(NGX_LOG_ALERT, s->connection->log, 0,
"too big RTMP chunk size:%ui", size);
return NGX_ERROR;
}
/* 获取 server{} 所属的 ngx_rtmp_conf_ctx_t 的 srv_conf[0],即对应的
* ngx_rtmp_core_module 的srv级别配置结构体,也对应该 server{} 的 配置结构体 */
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
s->in_old_pool = s->in_pool;
/* 设置 rtmp 块的大小 */
s->in_chunk_size = size;
s->in_pool = ngx_create_pool(4096, s->connection->log);
/* copy existing chunk data */
if (s->in_old_pool) {
s->in_chunk_size_changing = 1;
s->in_streams[0].in = NULL;
for(n = 1; n < cscf->max_streams; ++n) {
/* stream buffer is circular
* for all streams except for the current one
* (which caused this chunk size change);
* we can simply ignore it */
li = s->in_streams[n].in;
if (li == NULL || li->next == NULL) {
s->in_streams[n].in = NULL;
continue;
}
/* move from last to the first */
li = li->next;
fli = li;
lo = ngx_rtmp_alloc_in_buf(s);
if (lo == NULL) {
return NGX_ERROR;
}
flo = lo;
for ( ;; ) {
bi = li->buf;
bo = lo->buf;
if (bo->end - bo->last >= bi->last - bi->pos) {
bo->last = ngx_cpymem(bo->last, bi->pos,
bi->last - bi->pos);
li = li->next;
if (li == fli) {
lo->next = flo;
s->in_streams[n].in = lo;
break;
}
continue;
}
bi->pos += (ngx_cpymem(bo->last, bi->pos,
bo->end - bo->last) - bo->last);
lo->next = ngx_rtmp_alloc_in_buf(s);
lo = lo->next;
if (lo == NULL) {
return NGX_ERROR;
}
}
}
}
return NGX_OK;
}
该函数主要是设置 s->in_chunk_size(即 rtmp 块大小) 的大小,并为 s->in_pool 重新分配一个 4096 大小的内存池,
最后检测旧的内存池中是否有块数据,有则拷贝过来。
2.2.2 ngx_rtmp_fire_event
ngx_int_t ngx_rtmp_fire_event(ngx_rtmp_session_t *s, ngx_uint_t evt,
ngx_rtmp_header_t *h, ngx_chain_t *in)
{
ngx_rtmp_core_main_conf_t *cmcf;
ngx_array_t *ch;
ngx_rtmp_handler_pt *hh;
size_t n;
/* 获取 server{} 所属的 ngx_rtmp_conf_ctx_t 的 main_conf[0],即对应的
* ngx_rtmp_core_module 的main级别配置结构体 */
cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);
/* 根据 evt 的值在events数组中找到对应的存储着
* 所有 RTMP 对这类 evt 事件的回调方法,该回调
* 方法一般是在每个 RTMP 模块的 postconfiguration
* 方法中添加的 */
ch = &cmcf->events[evt];
hh = ch->elts;
for(n = 0; n < ch->nelts; ++n, ++hh) {
if (*hh && (*hh)(s, h, in) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
该函数主要是找到所有 RTMP 模块在 postconfiguration 方法中根据 evt 值保存在 events 数组中的回调函数,然后
一一调用它们,没有找到则返回。
2.2.3 NGX_RTMP_CONNECT 事件
该事件只有 ngx_rtmp_limit_module 模块设置有回调函数:
static ngx_int_t ngx_rtmp_limit_postconfiguration(ngx_conf_t *cf)
{
ngx_rtmp_core_main_conf_t *cmcf;
ngx_rtmp_handler_pt *h;
...
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
h = ngx_array_push(&cmcf->events[NGX_RTMP_CONNECT]);
*h = ngx_rtmp_limit_connect;
...
}
因此,在 ngx_rtmp_fire_event 方法中调用的对应 NGX_RTMP_CONNECT 事件的回调方法为 ngx_rtmp_limit_connect:
static ngx_int_t ngx_rtmp_limit_connect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
ngx_rtmp_limit_main_conf_t *lmcf;
ngx_slab_pool_t *shpool;
ngx_shm_zone_t *shm_zone;
uint32_t *nconn, n;
ngx_int_t rc;
/* 获取 server{} 所属的 ngx_rtmp_conf_ctx_t 的 main_conf 指针数组中
* ngx_rtmp_limit_module 对应的序号的 main 级别配置结构体 */
lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module);
/* 检测 nginx.conf 配置文件中是否设置了 max_connections 配置项,若没有
* 则直接返回 */
if (lmcf->max_conn == NGX_CONF_UNSET) {
return NGX_OK;
}
/* 下面是在配置文件有 max_connections 配置项的情况下检测当前的
* 连接数是否大于 max_connections 的值 */
shm_zone = lmcf->shm_zone;
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
nconn = shm_zone->data;
ngx_shmtx_lock(&shpool->mutex);
n = ++*nconn;
ngx_shmtx_unlock(&shpool->mutex);
rc = n > (ngx_uint_t) lmcf->max_conn ? NGX_ERROR : NGX_OK;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"limit: inc conection counter: %uD", n);
if (rc != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
"limit: too many connections: %uD > %i",
n, lmcf->max_conn);
}
return rc;
}
2.3 ngx_rtmp_handshake
在没有开启代理服务的情况下,初始化完一个 ngx_rtmp_session_t 后,直接进行 rtmp 的 handshake 过程。
void ngx_rtmp_handshake(ngx_rtmp_session_t *s)
{
ngx_connection_t *c;
c = s->connection;
/* 设置当前连接的读写事件回调方法 */
c->read->handler = ngx_rtmp_handshake_recv;
c->write->handler = ngx_rtmp_handshake_send;
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"handshake: start server handshake");
/* 分配 handshake 过程的数据缓存 */
s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);
/* 初始化当前 handshake 的阶段 */
s->hs_stage = NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE;
/* 开始读取客户端发来的 C0,C1 数据 */
ngx_rtmp_handshake_recv(c->read);
}
该函数主要设置读写事件的回调函数,分配 handhshake 过程的数据缓存空间,并初始化当前 handshake 阶段。
2.4 ngx_rtmp_handshake_recv
/* RTMP handshake :
*
* =peer1= =peer2=
* challenge ----> (.....[digest1]......) ----> 1537 bytes
* response <---- (...........[digest2]) <---- 1536 bytes
*
*
* - both packets contain random bytes except for digests
* - digest1 position is calculated on random packet bytes
* - digest2 is always at the end of the packet
*
* digest1: HMAC_SHA256(packet, peer1_partial_key)
* digest2: HMAC_SHA256(packet, HMAC_SHA256(digest1, peer2_full_key))
*/
/* Handshake keys */
static u_char
ngx_rtmp_server_key[] = {
'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ',
'S', 'e', 'r', 'v', 'e', 'r', ' ',
'0', '0', '1',
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,
0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
};
static u_char
ngx_rtmp_client_key[] = {
'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',
'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ',
'0', '0', '1',
0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,
0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,
0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE
};
static const u_char
ngx_rtmp_server_version[4] = {
0x0D, 0x0E, 0x0A, 0x0D
};
static const u_char
ngx_rtmp_client_version[4] = {
0x0C, 0x00, 0x0D, 0x0E
};
static ngx_str_t ngx_rtmp_server_full_key
= { sizeof(ngx_rtmp_server_key), ngx_rtmp_server_key };
static ngx_str_t ngx_rtmp_server_partial_key
= { 36, ngx_rtmp_server_key };
static ngx_str_t ngx_rtmp_client_full_key
= { sizeof(ngx_rtmp_client_key), ngx_rtmp_client_key };
static ngx_str_t ngx_rtmp_client_partial_key
= { 30, ngx_rtmp_client_key };
static void ngx_rtmp_handshake_recv(ngx_event_t *rev)
{
ssize_t n;
ngx_connection_t *c;
ngx_rtmp_session_t *s;
ngx_buf_t *b;
c = rev->data;
s = c->data;
/* 检测该连接是否已经被销毁了 */
if (c->destroyed) {
return;
}
/* 检测该读事件是否已经超时 */
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
"handshake: recv: client timed out");
c->timedout = 1;
ngx_rtmp_finalize_session(s);
return;
}
/*
* 每次调用recv没有接收到客户端的数据时,都会把该事件添加到epoll的
* 读事件中,同时将该事件也添加到定时器中,并设置该读事件的回调函数为
* ngx_rtmp_handshake_recv方法,因此,当监听到客户端发送的数据而再次
* 调用该方法时,需要将该事件从定时器中移除。
*/
if (rev->timer_set) {
ngx_del_timer(rev);
}
/* b 指向 handshake 的数据缓存首地址 */
b = s->hs_buf;
/* 当 handshake 的缓存未满时 */
while (b->last != b->end) {
/* 调用 c->recv 指向的回调函数接收数据,实际调用的是
* ngx_unix_recv 方法 */
n = c->recv(c, b->last, b->end - b->last);
if (n == NGX_ERROR || n == 0) {
ngx_rtmp_finalize_session(s);
return;
}
/* 若返回值为 NGX_AGAIN,则将该读事件再次添加到定时器中,并将
* 也添加到 epoll 等监控机制中 */
if (n == NGX_AGAIN) {
ngx_add_timer(rev, s->timeout);
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
ngx_rtmp_finalize_session(s);
}
return;
}
/* 若成功接收到数据,则更新 b->last 指针 */
b->last += n;
}
/* 将该读事件从 epoll 等事件监控机制中删除 */
if (rev->active) {
ngx_del_event(rev, NGX_READ_EVENT, 0);
}
/* 接收到客户端发来的数据后,更新当前 handshake 阶段 */
++s->hs_stage;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"handshake: stage %ui", s->hs_stage);
switch (s->hs_stage) {
/* 服务端发送 S0、S1 阶段 */
case NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE:
/*
* 此时接收到包应是 client 发来的 RTMP complex handshake 的 C0 和 C1 包.
* 因此解析 C0+C1,并进行验证. */
if (ngx_rtmp_handshake_parse_challenge(s,
&ngx_rtmp_client_partial_key,
&ngx_rtmp_server_full_key) != NGX_OK)
{
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"handshake: error parsing challenge");
ngx_rtmp_finalize_session(s);
return;
}
if (s->hs_old) {
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"handshake: old-style challenge");
s->hs_buf->pos = s->hs_buf->start;
s->hs_buf->last = s->hs_buf->end;
}
/* 解析完客户端发送来的 C0 和 C1 并进行验证成功后,接着构建 S0+S1,
* 发送给客户端. */
else if (ngx_rtmp_handshake_create_challenge(s,
ngx_rtmp_server_version,
&ngx_rtmp_server_partial_key) != NGX_OK)
{
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"handshake: error creating challenge");
ngx_rtmp_finalize_session(s);
return;
}
/* 发送 S0+S1,然后该函数接着构建 S2 并发送 */
ngx_rtmp_handshake_send(c->write);
break;
/* 服务端握手结束阶段 */
case NGX_RTMP_HANDSHAKE_SERVER_DONE:
/* 这里表示接收到了客户端发来的 handshake 过程的最后一个packet C2 */
ngx_rtmp_handshake_done(s);
break;
/* 作为客户端接受响应阶段 */
case NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE:
if (ngx_rtmp_handshake_parse_challenge(s,
&ngx_rtmp_server_partial_key,
&ngx_rtmp_client_full_key) != NGX_OK)
{
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"handshake: error parsing challenge");
ngx_rtmp_finalize_session(s);
return;
}
s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1;
ngx_rtmp_handshake_recv(c->read);
break;
/* 作为客户端发送响应阶段 */
case NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE:
if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"handshake: response error");
ngx_rtmp_finalize_session(s);
return;
}
ngx_rtmp_handshake_send(c->write);
break;
}
}
2.5 ngx_rtmp_handshake_parse_challenge
/*
* RTMP Complex handshake:
*
* 抓包知:C0+C1共1537 bytes,C0 1 byte,C1 1536 bytes.
*
* C0: 1byte,即rtmp的版本号"x03"
*
* C1和S1包含两部分数据:key和digest,分别为如下:
* - time: 4 bytes
* - version: 4 bytes
* - key: 764 bytes
* - digest: 764 bytes
*
* key和digest的顺序是不确定的,也有可能是:(nginx-rtmp中是如下的顺序)
* - time: 4 bytes
* - version: 4 bytes
* - digest: 764 bytes
* - key: 764 bytes
*
* 764 bytes key 结构:
* - random-data: (offset) bytes
* - key-data: 128 bytes
* - random-data: (764 - offset - 128 - 4) bytes
* - offset: 4 bytes
*
* 764 bytes digest结构:
* - offset: 4 bytes
* - random-data: (offset) bytes
* - digest-data: 32 bytes
* - random-data: (764 - 4 - offset - 32) bytes
*/
static ngx_int_t ngx_rtmp_handshake_parse_challenge(ngx_rtmp_session_t *s,
ngx_str_t *peer_key, ngx_str_t *key)
{
ngx_buf_t *b;
u_char *p;
ngx_int_t offs;
/* C0或S0: 第一个字节必须是 RTMP 协议的版本号"03" */
b = s->hs_buf;
if (*b->pos != 'x03') {
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
"handshake: unexpected RTMP version: %i",
(ngx_int_t)*b->pos);
return NGX_ERROR;
}
/* 接下来是 C1 或 S1 */
/* 版本号之后是客户端的 epoch 时间 */
++b->pos;
s->peer_epoch = 0;
ngx_rtmp_rmemcpy(&s->peer_epoch, b->pos, 4);
/* 再接下来的四字节是客户端的版本号 */
p = b->pos + 4;
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"handshake: peer version=%i.%i.%i.%i epoch=%uD",
(ngx_int_t)p[3], (ngx_int_t)p[2],
(ngx_int_t)p[1], (ngx_int_t)p[0],
(uint32_t)s->peer_epoch);
if (*(uint32_t *)p == 0) {
s->hs_old = 1;
return NGX_OK;
}
/* 找到key和digest,进行验证 */
offs = ngx_rtmp_find_digest(b, peer_key, 772, s->connection->log);
if (offs == NGX_ERROR) {
offs = ngx_rtmp_find_digest(b, peer_key, 8, s->connection->log);
}
if (offs == NGX_ERROR) {
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
"handshake: digest not found");
s->hs_old = 1;
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"handshake: digest found at pos=%i", offs);
/* 这里 b->pos 和 b->last 分别指向找到 digest-data 数据区的起始和结尾地址 */
b->pos += offs;
b->last = b->pos + NGX_RTMP_HANDSHAKE_KEYLEN;
/* 下面是计算服务器的 digest */
s->hs_digest = ngx_palloc(s->connection->pool, NGX_RTMP_HANDSHAKE_KEYLEN);
if (ngx_rtmp_make_digest(key, b, NULL, s->hs_digest, s->connection->log)
!= NGX_OK)
{
return NGX_ERROR;
}
return NGX_OK;
}
2.5.1 ngx_rtmp_find_digest
/* 当 base 为 772 时,此时假设 C1 数据的结构如下:
* - time: 4 bytes
* - version: 4 bytes
* - key: 764 bytes
* - digest: 764 bytes
* 此时查找 digest,base 为 772,即跳过 C1 数据的 time、version、keys 这前三部分共 772 字节的数据,
* 从 digest 数据开始查找真正的 digest-data.
*
* 当 base 为 8 时,此时假设 C1 数据的结构如下:
* - time: 4 bytes
* - version: 4 bytes
* - digest: 764 bytes
* - key: 764 bytes
* 此时查找 digest,base 为 8,即跳过 C1 数据的 time、version 这前两部分共 8 字节的数据,
* 从 digest 数据开始查找真正的 digest-data.
*
* 764 bytes digest结构:
* - offset: 4 bytes
* - random-data: (offset) bytes
* - digest-data: 32 bytes
* - random-data: (764 - 4 - offset - 32 = 728 - offset) bytes
*
* 参考上面的digest结构,查找算法是:将 digest 数据开始的前 4 字节相加得到 offs,即 offset 值,
* 然后将 offs 除以 728 后取余数,再加上 base + 4,得出 digest-data 的在 C1 数据中的偏移值
*
*/
static ngx_int_t ngx_rtmp_find_digest(ngx_buf_t *b, ngx_str_t *key, size_t base, ngx_log_t *log)
{
size_t n, offs;
u_char digest[NGX_RTMP_HANDSHAKE_KEYLEN];
u_char *p;
offs = 0;
/* 将 digest 数据开始的前 4 字节的值相加 得到 offset */
for (n = 0; n < 4; ++n) {
offs += b->pos[base + n];
}
offs = (offs % 728) + base + 4;
/* p 指向 digest-data 的起始地址 */
p = b->pos + offs;
if (ngx_rtmp_make_digest(key, b, p, digest, log) != NGX_OK) {
return NGX_ERROR;
}
/* 校验计算出来的 digest 与 p 指向的 digest-data 是否相同,相同表示校验
* 成功,即找到了正确的 digest-data 的偏移值 */
if (ngx_memcmp(digest, p, NGX_RTMP_HANDSHAKE_KEYLEN) == 0) {
return offs;
}
return NGX_ERROR;
}
2.5.2 ngx_rtmp_make_digest
static ngx_int_t ngx_rtmp_make_digest(ngx_str_t *key, ngx_buf_t *src,
u_char *skip, u_char *dst, ngx_log_t *log)
{
static HMAC_CTX *hmac;
unsigned int len;
if (hmac == NULL) {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
static HMAC_CTX shmac;
hmac = &shmac;
/* 初始化 hmac,在计算 MAC 之前必须调用此函数 */
HMAC_CTX_init(hmac);
#else
hmac = HMAC_CTX_new();
if (hmac == NULL) {
return NGX_ERROR;
}
#endif
}
/* 初始化 hmac 数据,在 hmac 中复制 EVP_256() 方法,密钥数据,
* 密钥数据长度len,最后一个参数为 NULL */
HMAC_Init_ex(hmac, key->data, key->len, EVP_sha256(), NULL);
if (skip && src->pos <= skip && skip <= src->last) {
if (skip != src->pos) {
/* 将数据块加入到计算结果中,数据缓冲区 src->pos,数据长度skip - src->pos */
HMAC_Update(hmac, src->pos, skip - src->pos);
}
if (src->last != skip + NGX_RTMP_HANDSHAKE_KEYLEN) {
HMAC_Update(hmac, skip + NGX_RTMP_HANDSHAKE_KEYLEN,
src->last - skip - NGX_RTMP_HANDSHAKE_KEYLEN);
}
} else {
HMAC_Update(hmac, src->pos, src->last - src->pos);
}
/* 计算出的 MAC 数据放置在 dst 中,用户需要保证 dst 数据有足够长的空间,长度为 len */
HMAC_Final(hmac, dst, &len);
return NGX_OK;
}
散列消息鉴别码,简称 HMAC,是一种基于消息鉴别码 MAC(Message Authentication Code)的鉴别机制。使用 HMAC 时,
消息通讯的双方,通过验证消息中加入的鉴别密钥 K 来鉴别消息的真伪.
2.6 ngx_rtmp_handshake_create_challenge
/*
* RTMP complex handshake: S0+S1
*
* 抓包知,S0 1 byte,S1和S2都为1536 bytes
*
* S0: 1 byte,为rtmp版本号"x03"
*
* S1: 1536 bytes (key和digest有些会调换一下)
* - time: 4 bytes
* - version: 4 bytes
* - key: 764 bytes
* - digest: 764 bytes
*
* 该函数仅构建了 S0+S1 共1537字节
*/
static ngx_int_t ngx_rtmp_handshake_create_challenge(ngx_rtmp_session_t *s,
const u_char version[4], ngx_str_t *key)
{
ngx_buf_t *b;
b = s->hs_buf;
b->last = b->pos = b->start;
/* S0: 1 byte */
*b->last++ = 'x03';
/* S1 */
/* - time: 4 bytes */
b->last = ngx_rtmp_rcpymem(b->last, &s->epoch, 4);
/* - version: 4 bytes */
b->last = ngx_cpymem(b->last, version, 4);
/* 之后的缓存先填满随机数据 */
ngx_rtmp_fill_random_buffer(b);
++b->pos; // 先略过 S0
/* 写入服务器的 digest-data */
if (ngx_rtmp_write_digest(b, key, 0, s->connection->log) != NGX_OK) {
return NGX_ERROR;
}
--b->pos; // 再恢复到指向 S0 位置
return NGX_OK;
}
2.6.1 ngx_rtmp_write_digest
/* 在 Nginx-rtmp 中 S1 的数据结构使用如下:
* - time: 4 bytes
* - version: 4 bytes
* - digest: 764 bytes
* - key: 764 bytes
*
* 764 bytes digest 结构:
* - offset: 4 bytes
* - random-data: (offset) bytes
* - digest-data: 32 bytes
* - random-data: (764 - 4 - offset - 32 = 728 - offset) bytes
*/
static ngx_int_t ngx_rtmp_write_digest(ngx_buf_t *b, ngx_str_t *key, size_t base,
ngx_log_t *log)
{
size_t n, offs;
u_char *p;
offs = 0;
/* 将 b->pos[8] ~ b->pos[11] 这四个值相加得到 offset */
for (n = 8; n < 12; ++n) {
offs += b->pos[base + n];
}
offs = (offs % 728) + base + 12;
p = b->pos + offs;
/* 生成 digest 并存放在 p 指向的位置 */
if (ngx_rtmp_make_digest(key, b, p, p, log) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK;
}
2.7 ngx_rtmp_handshake_send
static void ngx_rtmp_handshake_send(ngx_event_t *wev)
{
ngx_int_t n;
ngx_connection_t *c;
ngx_rtmp_session_t *s;
ngx_buf_t *b;
c = wev->data;
s = c->data;
if (c->destroyed) {
return;
}
if (wev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
"handshake: send: client timed out");
c->timedout = 1;
ngx_rtmp_finalize_session(s);
return;
}
/* 将 wev 写事件从定时器中移除 */
if (wev->timer_set) {
ngx_del_timer(wev);
}
b = s->hs_buf;
/* 当 handshake 缓存中有数据时 */
while(b->pos != b->last) {
/* 调用 c->send 指向的回调函数(即 ngx_unix_send 方法)发送数据 */
n = c->send(c, b->pos, b->last - b->pos);
if (n == NGX_ERROR) {
ngx_rtmp_finalize_session(s);
return;
}
if (n == NGX_AGAIN || n == 0) {
ngx_add_timer(c->write, s->timeout);
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
ngx_rtmp_finalize_session(s);
}
return;
}
b->pos += n;
}
/* 若 wev 写事件为活跃的,则将其从 epoll 等事件监控机制中移除 */
if (wev->active) {
ngx_del_event(wev, NGX_WRITE_EVENT, 0);
}
/* 更新当前 handshake 阶段 */
++s->hs_stage;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"handshake: stage %ui", s->hs_stage);
switch (s->hs_stage) {
case NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE:
if (s->hs_old) {
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"handshake: old-style response");
s->hs_buf->pos = s->hs_buf->start + 1;
s->hs_buf->last = s->hs_buf->end;
}
/* 这里构建 S2 */
else if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"handshake: response error");
ngx_rtmp_finalize_session(s);
return;
}
/* 发送 S2 */
ngx_rtmp_handshake_send(wev);
break;
case NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE:
s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1;
ngx_rtmp_handshake_recv(c->read);
break;
case NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE:
s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start;
ngx_rtmp_handshake_recv(c->read);
break;
case NGX_RTMP_HANDSHAKE_CLIENT_DONE:
ngx_rtmp_handshake_done(s);
break;
}
}
2.8 ngx_rtmp_handshake_create_response
/*
* RTMP complex handshake: C2/S2
* S2: 前 1504 bytes数据随机生成(前 8 bytes需要注意),然后对 1504 bytes 数据进行HMACsha256
* 得到digest,将digest放到最后的32bytes。
*
* 1536 bytes C2/S2 结构
* random-data: 1504 bytes
* digest-data: 32 bytes
*/
static ngx_int_t ngx_rtmp_handshake_create_response(ngx_rtmp_session_t *s)
{
ngx_buf_t *b;
u_char *p;
ngx_str_t key;
b = s->hs_buf;
b->pos = b->last = b->start + 1;
ngx_rtmp_fill_random_buffer(b);
if (s->hs_digest) {
/* p 指向最后的 32 bytes 的首地址处 */
p = b->last - NGX_RTMP_HANDSHAKE_KEYLEN;
key.data = s->hs_digest;
key.len = NGX_RTMP_HANDSHAKE_KEYLEN;
/* 将生成的 digest 放置到 s2 数据的最后 32 bytes 中 */
if (ngx_rtmp_make_digest(&key, b, p, p, s->connection->log) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}
2.9 ngx_rtmp_handshake_done
static void ngx_rtmp_handshake_done(ngx_rtmp_session_t *s)
{
/* 释放 handshake 过程使用的缓存 hs_buf,其实,主要是将其
* 插入到 ngx_rtmp_core_srv_conf_t 的 free_hs 成员所持的
* 链表表头 */
ngx_rtmp_free_handshake_buffers(s);
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"handshake: done");
/* 在 events 数组中查找是否有 RTMP 模块设置有 NGX_RTMP_HANDSHAKE_DONE 的回调
* 方法,有则一一调用,否则不做任何处理 */
if (ngx_rtmp_fire_event(s, NGX_RTMP_HANDSHAKE_DONE,
NULL, NULL) != NGX_OK)
{
ngx_rtmp_finalize_session(s);
return;
}
/* 下面进入 rtmp 业务循环 */
ngx_rtmp_cycle(s);
}
在所有的 RTMP 模块中,仅有 ngx_rtmp_relay_module 模块设置了 NGX_RTMP_HANDSHAKE_DONE 的回调方法:
static ngx_int_t ngx_rtmp_relay_postconfiguration(ngx_conf_t *cf)
{
ngx_rtmp_core_main_conf_t *cmcf;
ngx_rtmp_handler_pt *h;
...
cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);
h = ngx_array_push(&cmcf->events[NGX_RTMP_HANDSHAKE_DONE]);
*h = ngx_rtmp_relay_handshake_done;
...
}
2.9.1 ngx_rtmp_relay_handshake_done
static ngx_int_t ngx_rtmp_relay_handshake_done(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
ngx_chain_t *in)
{
ngx_rtmp_relay_ctx_t *ctx;
/* 获取 ngx_rtmp_relay_module 模块的上下文结构体 */
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
/* 若 relay 为 0,则直接返回,这里是为 0,因为没有使用 relay 模块的相关功能 */
if (ctx == NULL || !s->relay) {
return NGX_OK;
}
return ngx_rtmp_relay_send_connect(s);
}
S -> C: S0 + S1 + S2
以上就是 nginx-rtmp 握手的全过程。