Merge branch 'handshakefix'

This commit is contained in:
Roman Arutyunyan 2012-05-21 13:09:57 +04:00
commit 6e471119cc
2 changed files with 130 additions and 139 deletions

View file

@ -194,7 +194,9 @@ typedef struct {
ngx_str_t page_url; ngx_str_t page_url;
/* handshake data */ /* handshake data */
ngx_buf_t *hs_buf, *hs_bufs[3]; ngx_buf_t *hs_buf;
u_char *hs_digest;
unsigned hs_old:1;
ngx_uint_t hs_stage; ngx_uint_t hs_stage;
/* connection timestamps */ /* connection timestamps */

View file

@ -14,6 +14,22 @@ static void ngx_rtmp_handshake_recv(ngx_event_t *rev);
static void ngx_rtmp_handshake_done(ngx_rtmp_session_t *s); static void ngx_rtmp_handshake_done(ngx_rtmp_session_t *s);
/* 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 */ /* Handshake keys */
static u_char static u_char
ngx_rtmp_server_key[] = { ngx_rtmp_server_key[] = {
@ -75,6 +91,8 @@ static ngx_str_t ngx_rtmp_server_full_key
static ngx_str_t ngx_rtmp_server_partial_key static ngx_str_t ngx_rtmp_server_partial_key
= { 36, ngx_rtmp_server_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 static ngx_str_t ngx_rtmp_client_partial_key
= { 30, ngx_rtmp_client_key }; = { 30, ngx_rtmp_client_key };
@ -166,15 +184,14 @@ ngx_rtmp_fill_random_buffer(ngx_buf_t *b)
static ngx_buf_t * static ngx_buf_t *
ngx_rtmp_alloc_handshake_buffer(ngx_rtmp_session_t *s, int short_buf) ngx_rtmp_alloc_handshake_buffer(ngx_rtmp_session_t *s)
{ {
ngx_rtmp_core_srv_conf_t *cscf; ngx_rtmp_core_srv_conf_t *cscf;
ngx_chain_t *cl; ngx_chain_t *cl;
ngx_buf_t *b; ngx_buf_t *b;
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"handshake: allocating %sbuffer", "handshake: allocating buffer");
short_buf ? "short " : "");
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
@ -197,90 +214,67 @@ ngx_rtmp_alloc_handshake_buffer(ngx_rtmp_session_t *s, int short_buf)
b->end = b->start + NGX_RTMP_HANDSHAKE_BUFSIZE; b->end = b->start + NGX_RTMP_HANDSHAKE_BUFSIZE;
} }
if (short_buf) { b->pos = b->last = b->start;
b->pos = b->last = b->start + 1;
} else {
b->pos = b->last = b->start;
}
return b; return b;
} }
static ngx_int_t
ngx_rtmp_free_handshake_buffer(ngx_rtmp_session_t *s, ngx_buf_t *b)
{
ngx_rtmp_core_srv_conf_t *cscf;
ngx_chain_t *cl;
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
cl = ngx_alloc_chain_link(cscf->pool);
if (cl == NULL) {
return NGX_ERROR;
}
cl->buf = b;
cl->next = cscf->free_hs;
cscf->free_hs = cl;
return NGX_OK;
}
void void
ngx_rtmp_free_handshake_buffers(ngx_rtmp_session_t *s) ngx_rtmp_free_handshake_buffers(ngx_rtmp_session_t *s)
{ {
size_t n; ngx_rtmp_core_srv_conf_t *cscf;
ngx_chain_t *cl;
for (n = 0; n < sizeof(s->hs_bufs) / sizeof(s->hs_bufs[0]); ++n) { if (s->hs_buf == NULL) {
if (s->hs_bufs[n]) { return;
ngx_rtmp_free_handshake_buffer(s, s->hs_bufs[n]);
s->hs_bufs[n] = NULL;
}
} }
cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
cl = ngx_alloc_chain_link(cscf->pool);
if (cl == NULL) {
return;
}
cl->buf = s->hs_buf;
cl->next = cscf->free_hs;
cscf->free_hs = cl;
s->hs_buf = NULL;
} }
static ngx_int_t static ngx_int_t
ngx_rtmp_old_handshake_response(ngx_rtmp_session_t *s) ngx_rtmp_handshake_create_challenge(ngx_rtmp_session_t *s,
const u_char version[4], ngx_str_t *key)
{ {
ngx_buf_t *b; ngx_buf_t *b;
u_char *src;
size_t len;
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, b = s->hs_buf;
"handshake: old-style handshake"); b->last = b->pos = b->start;
src = s->hs_bufs[0]->pos + 8;
len = s->hs_bufs[0]->last - src;
b = s->hs_bufs[1];
*b->last++ = '\x03'; *b->last++ = '\x03';
b->last = ngx_rtmp_rcpymem(b->last, &s->epoch, 4); b->last = ngx_rtmp_rcpymem(b->last, &s->epoch, 4);
ngx_memzero(b->last, 4); b->last = ngx_cpymem(b->last, version, 4);
b->last = ngx_cpymem(b->last + 4, src, len); ngx_rtmp_fill_random_buffer(b);
++b->pos;
b = s->hs_bufs[2]; if (ngx_rtmp_write_digest(b, key, 0, s->connection->log) != NGX_OK) {
b->last = ngx_rtmp_rcpymem(b->last, &s->peer_epoch, 4); return NGX_ERROR;
ngx_memzero(b->last, 4); }
b->last = ngx_cpymem(b->last + 4, src, len); --b->pos;
return NGX_OK; return NGX_OK;
} }
static ngx_int_t static ngx_int_t
ngx_rtmp_handshake_response(ngx_rtmp_session_t *s) ngx_rtmp_handshake_parse_challenge(ngx_rtmp_session_t *s,
ngx_str_t *peer_key, ngx_str_t *key)
{ {
u_char *p;
ngx_buf_t *b; ngx_buf_t *b;
u_char *p;
ngx_int_t offs; ngx_int_t offs;
u_char digest[NGX_RTMP_HANDSHAKE_KEYLEN];
ngx_str_t key;
/* read input buffer */ b = s->hs_buf;
b = s->hs_bufs[0];
if (*b->pos != '\x03') { if (*b->pos != '\x03') {
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
"handshake: unexpected RTMP version: %i", (ngx_int_t)*b->pos); "handshake: unexpected RTMP version: %i",
(ngx_int_t)*b->pos);
return NGX_ERROR; return NGX_ERROR;
} }
++b->pos; ++b->pos;
@ -288,93 +282,58 @@ ngx_rtmp_handshake_response(ngx_rtmp_session_t *s)
p = b->pos + 4; p = b->pos + 4;
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"handshake: client version=%i.%i.%i.%i epoch=%uD", "handshake: peer version=%i.%i.%i.%i epoch=%uD",
(ngx_int_t)p[3], (ngx_int_t)p[2], (ngx_int_t)p[3], (ngx_int_t)p[2],
(ngx_int_t)p[1], (ngx_int_t)p[0], (ngx_int_t)p[1], (ngx_int_t)p[0],
s->peer_epoch); s->peer_epoch);
if (*(uint32_t *)p == 0) { if (*(uint32_t *)p == 0) {
return ngx_rtmp_old_handshake_response(s); s->hs_old = 1;
return NGX_OK;
} }
offs = ngx_rtmp_find_digest(b, &ngx_rtmp_client_partial_key, offs = ngx_rtmp_find_digest(b, peer_key, 772, s->connection->log);
772, s->connection->log);
if (offs == NGX_ERROR) { if (offs == NGX_ERROR) {
offs = ngx_rtmp_find_digest(b, &ngx_rtmp_client_partial_key, offs = ngx_rtmp_find_digest(b, peer_key, 8, s->connection->log);
8, s->connection->log);
} }
if (offs == NGX_ERROR) { if (offs == NGX_ERROR) {
ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
"handshake: digest not found"); "handshake: digest not found");
return ngx_rtmp_old_handshake_response(s); s->hs_old = 1;
} return NGX_OK;
b->pos += offs;
b->last = b->pos + NGX_RTMP_HANDSHAKE_KEYLEN;
if (ngx_rtmp_make_digest(&ngx_rtmp_server_full_key, b,
NULL, digest, s->connection->log) != NGX_OK)
{
return NGX_ERROR;
} }
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"handshake: digest found at pos=%i", offs); "handshake: digest found at pos=%i", offs);
b->pos += offs;
/* create first output buffer */ b->last = b->pos + NGX_RTMP_HANDSHAKE_KEYLEN;
b = s->hs_bufs[1]; s->hs_digest = ngx_palloc(s->connection->pool, NGX_RTMP_HANDSHAKE_KEYLEN);
*b->last++ = '\x03'; if (ngx_rtmp_make_digest(key, b, NULL, s->hs_digest, s->connection->log)
b->last = ngx_rtmp_rcpymem(b->last, &s->epoch, 4); != NGX_OK)
b->last = ngx_cpymem(b->last, ngx_rtmp_server_version, 4);
ngx_rtmp_fill_random_buffer(b);
++b->pos;
if (ngx_rtmp_write_digest(b, &ngx_rtmp_server_partial_key,
0, s->connection->log) != NGX_OK)
{ {
return NGX_ERROR; return NGX_ERROR;
} }
--b->pos;
/* create second output buffer */
b = s->hs_bufs[2];
ngx_rtmp_fill_random_buffer(b);
key.data = digest;
key.len = sizeof(digest);
p = b->last - key.len;
if (ngx_rtmp_make_digest(&key, b, p, p, s->connection->log) != NGX_OK) {
return NGX_ERROR;
}
return NGX_OK; return NGX_OK;
} }
static ngx_int_t static ngx_int_t
ngx_rtmp_handshake_client_response(ngx_rtmp_session_t *s) ngx_rtmp_handshake_create_response(ngx_rtmp_session_t *s)
{
/*TODO: implement good client response generation
* to make it possible relaying data from/to FMS.
*
* This module as server ignores the last response
* from client. */
return NGX_OK;
}
static ngx_int_t
ngx_rtmp_handshake_make_client_request(ngx_rtmp_session_t *s)
{ {
ngx_buf_t *b; ngx_buf_t *b;
u_char *p;
ngx_str_t key;
b = s->hs_bufs[0]; b = s->hs_buf;
*b->last++ = '\x03'; b->pos = b->last = b->start + 1;
b->last = ngx_rtmp_rcpymem(b->last, &s->epoch, 4);
b->last = ngx_rtmp_rcpymem(b->last, ngx_rtmp_client_version, 4);
ngx_rtmp_fill_random_buffer(b); ngx_rtmp_fill_random_buffer(b);
++b->pos; if (s->hs_digest) {
if (ngx_rtmp_write_digest(b, &ngx_rtmp_client_partial_key, p = b->last - NGX_RTMP_HANDSHAKE_KEYLEN;
0, s->connection->log) != NGX_OK) { key.data = s->hs_digest;
return NGX_ERROR; key.len = NGX_RTMP_HANDSHAKE_KEYLEN;
if (ngx_rtmp_make_digest(&key, b, p, p, s->connection->log) != NGX_OK) {
return NGX_ERROR;
}
} }
--b->pos;
return NGX_OK; return NGX_OK;
} }
@ -456,15 +415,29 @@ ngx_rtmp_handshake_recv(ngx_event_t *rev)
switch (s->hs_stage) { switch (s->hs_stage) {
case NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE: case NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE:
s->hs_bufs[1] = ngx_rtmp_alloc_handshake_buffer(s, 0); if (ngx_rtmp_handshake_parse_challenge(s,
s->hs_bufs[2] = ngx_rtmp_alloc_handshake_buffer(s, 1); &ngx_rtmp_client_partial_key,
if (ngx_rtmp_handshake_response(s) != NGX_OK) { &ngx_rtmp_server_full_key) != NGX_OK)
{
ngx_log_error(NGX_LOG_INFO, c->log, 0, ngx_log_error(NGX_LOG_INFO, c->log, 0,
"handshake: response error"); "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;
} 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); ngx_rtmp_finalize_session(s);
return; return;
} }
s->hs_buf = s->hs_bufs[1];
ngx_rtmp_handshake_send(c->write); ngx_rtmp_handshake_send(c->write);
break; break;
@ -473,19 +446,26 @@ ngx_rtmp_handshake_recv(ngx_event_t *rev)
break; break;
case NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE: case NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE:
s->hs_bufs[2] = ngx_rtmp_alloc_handshake_buffer(s, 1); if (ngx_rtmp_handshake_parse_challenge(s,
s->hs_buf = s->hs_bufs[2]; &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); ngx_rtmp_handshake_recv(c->read);
break; break;
case NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE: case NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE:
if (ngx_rtmp_handshake_client_response(s) != NGX_OK) { if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {
ngx_log_error(NGX_LOG_INFO, c->log, 0, ngx_log_error(NGX_LOG_INFO, c->log, 0,
"handshake: client response error"); "handshake: response error");
ngx_rtmp_finalize_session(s); ngx_rtmp_finalize_session(s);
return; return;
} }
s->hs_buf = s->hs_bufs[2];
ngx_rtmp_handshake_send(c->write); ngx_rtmp_handshake_send(c->write);
break; break;
} }
@ -550,19 +530,27 @@ ngx_rtmp_handshake_send(ngx_event_t *wev)
switch (s->hs_stage) { switch (s->hs_stage) {
case NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE: case NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE:
s->hs_buf = s->hs_bufs[2]; 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;
} 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;
}
ngx_rtmp_handshake_send(wev); ngx_rtmp_handshake_send(wev);
break; break;
case NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE: case NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE:
s->hs_buf = s->hs_bufs[0];
s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1; s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1;
ngx_rtmp_handshake_recv(c->read); ngx_rtmp_handshake_recv(c->read);
break; break;
case NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE: case NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE:
s->hs_bufs[1] = ngx_rtmp_alloc_handshake_buffer(s, 0); s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start;
s->hs_buf = s->hs_bufs[1];
ngx_rtmp_handshake_recv(c->read); ngx_rtmp_handshake_recv(c->read);
break; break;
@ -585,8 +573,7 @@ ngx_rtmp_handshake(ngx_rtmp_session_t *s)
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"handshake: start server handshake"); "handshake: start server handshake");
s->hs_bufs[0] = ngx_rtmp_alloc_handshake_buffer(s, 0); s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);
s->hs_buf = s->hs_bufs[0];
s->hs_stage = NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE; s->hs_stage = NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE;
ngx_rtmp_handshake_recv(c->read); ngx_rtmp_handshake_recv(c->read);
@ -605,11 +592,13 @@ ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async)
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"handshake: start client handshake"); "handshake: start client handshake");
s->hs_bufs[0] = ngx_rtmp_alloc_handshake_buffer(s, 0); s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);
s->hs_buf = s->hs_bufs[0];
s->hs_stage = NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE; s->hs_stage = NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE;
if (ngx_rtmp_handshake_make_client_request(s) != NGX_OK) { if (ngx_rtmp_handshake_create_challenge(s,
ngx_rtmp_client_version,
&ngx_rtmp_client_partial_key) != NGX_OK)
{
ngx_rtmp_finalize_session(s); ngx_rtmp_finalize_session(s);
return; return;
} }