Nginx-rtmp on-demand complex handshake

1. On-demand configuration

Suppose the configuration file nginx.conf configures rtmp as follows:

# 创建的子进程数
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. The handshake process

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;
}

This function mainly sets the size of s->in_chunk_size (that is, the rtmp block size), and reallocates a memory pool of size 4096 for s->in_pool, and
finally checks whether there is block data in the old memory pool, and copies it over.

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;
}

This function mainly finds all the callback functions stored in the events array according to the evt value of the RTMP module in the postconfiguration method, and then
calls them one by one, and returns if they are not found.

2.2.3 NGX_RTMP_CONNECT event

Only the ngx_rtmp_limit_module module has a callback function for this event:

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;

    ...
}

Therefore, the callback method corresponding to the NGX_RTMP_CONNECT event called in the ngx_rtmp_fire_event method is 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

If the proxy service is not turned on, after an ngx_rtmp_session_t is initialized, the handshake process of rtmp is performed directly.

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

This function mainly sets the callback function of the read and write events, allocates the data buffer space of the handshake process, and initializes the current handshake stage.

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;
}

Hash Message Authentication Code, or HMAC for short, is an authentication mechanism based on Message Authentication Code (MAC). When using HMAC,
the two parties in message communication verify the authenticity of the message by verifying the authentication key K added in the message.

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

Among all RTMP modules, only the ngx_rtmp_relay_module module sets the callback method of 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);
}

The above is the whole process of the nginx-rtmp handshake.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325163805&siteId=291194637