Commit e6350334 authored by Timothy B. Terriberry's avatar Timothy B. Terriberry

Update IPv4/IPv6 dual stack to RFC 6555.

RFC 6555 "Happy Eyeballs" has a few recommendations for
 implementing dual requests to hosts with both IPv4 and IPv6 DNS
 entries that differ slightly from how we used to do it.
This commit updates things to follow those recommendations.
parent bad75759
...@@ -44,7 +44,8 @@ ...@@ -44,7 +44,8 @@
RFC 6066: Transport Layer Security (TLS) Extensions: Extension Definitions RFC 6066: Transport Layer Security (TLS) Extensions: Extension Definitions
RFC 6125: Representation and Verification of Domain-Based Application Service RFC 6125: Representation and Verification of Domain-Based Application Service
Identity within Internet Public Key Infrastructure Using X.509 (PKIX) Identity within Internet Public Key Infrastructure Using X.509 (PKIX)
Certificates in the Context of Transport Layer Security (TLS)*/ Certificates in the Context of Transport Layer Security (TLS)
RFC 6555: Happy Eyeballs: Success with Dual-Stack Hosts*/
typedef struct OpusParsedURL OpusParsedURL; typedef struct OpusParsedURL OpusParsedURL;
typedef struct OpusStringBuf OpusStringBuf; typedef struct OpusStringBuf OpusStringBuf;
...@@ -358,6 +359,11 @@ typedef int op_sock; ...@@ -358,6 +359,11 @@ typedef int op_sock;
when seeking, and time out rapidly.*/ when seeking, and time out rapidly.*/
# define OP_NCONNS_MAX (4) # define OP_NCONNS_MAX (4)
/*The amount of time before we attempt to re-resolve the host.
This is 10 minutes, as recommended in RFC 6555 for expiring cached connection
results for dual-stack hosts.*/
# define OP_RESOLVE_CACHE_TIMEOUT_MS (10*60*(opus_int32)1000)
/*The number of redirections at which we give up. /*The number of redirections at which we give up.
The value here is the current default in Firefox. The value here is the current default in Firefox.
RFC 2068 mandated a maximum of 5, but RFC 2616 relaxed that to "a client RFC 2068 mandated a maximum of 5, but RFC 2616 relaxed that to "a client
...@@ -831,6 +837,8 @@ struct OpusHTTPStream{ ...@@ -831,6 +837,8 @@ struct OpusHTTPStream{
struct sockaddr_in v4; struct sockaddr_in v4;
struct sockaddr_in6 v6; struct sockaddr_in6 v6;
} addr; } addr;
/*The last time we re-resolved the host.*/
struct timeb resolve_time;
/*A buffer used to build HTTP requests.*/ /*A buffer used to build HTTP requests.*/
OpusStringBuf request; OpusStringBuf request;
/*A buffer used to build proxy CONNECT requests.*/ /*A buffer used to build proxy CONNECT requests.*/
...@@ -842,6 +850,10 @@ struct OpusHTTPStream{ ...@@ -842,6 +850,10 @@ struct OpusHTTPStream{
opus_int64 content_length; opus_int64 content_length;
/*The position indicator used when no connection is active.*/ /*The position indicator used when no connection is active.*/
opus_int64 pos; opus_int64 pos;
/*The host we actually connected to.*/
char *connect_host;
/*The port we actually connected to.*/
unsigned connect_port;
/*The connection we're currently reading from. /*The connection we're currently reading from.
This can be -1 if no connection is active.*/ This can be -1 if no connection is active.*/
int cur_conni; int cur_conni;
...@@ -875,6 +887,7 @@ static void op_http_stream_init(OpusHTTPStream *_stream){ ...@@ -875,6 +887,7 @@ static void op_http_stream_init(OpusHTTPStream *_stream){
op_sb_init(&_stream->request); op_sb_init(&_stream->request);
op_sb_init(&_stream->proxy_connect); op_sb_init(&_stream->proxy_connect);
op_sb_init(&_stream->response); op_sb_init(&_stream->response);
_stream->connect_host=NULL;
_stream->seekable=0; _stream->seekable=0;
} }
...@@ -914,6 +927,7 @@ static void op_http_stream_clear(OpusHTTPStream *_stream){ ...@@ -914,6 +927,7 @@ static void op_http_stream_clear(OpusHTTPStream *_stream){
op_sb_clear(&_stream->response); op_sb_clear(&_stream->response);
op_sb_clear(&_stream->proxy_connect); op_sb_clear(&_stream->proxy_connect);
op_sb_clear(&_stream->request); op_sb_clear(&_stream->request);
if(_stream->connect_host!=_stream->url.host)_ogg_free(_stream->connect_host);
op_parsed_url_clear(&_stream->url); op_parsed_url_clear(&_stream->url);
} }
...@@ -1866,9 +1880,9 @@ static int op_http_conn_start_tls(OpusHTTPStream *_stream,OpusHTTPConn *_conn, ...@@ -1866,9 +1880,9 @@ static int op_http_conn_start_tls(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
left to try. left to try.
*_addr will be set to NULL in this case.*/ *_addr will be set to NULL in this case.*/
static int op_sock_connect_next(op_sock _fd, static int op_sock_connect_next(op_sock _fd,
struct addrinfo **_addr,int _ai_family){ const struct addrinfo **_addr,int _ai_family){
struct addrinfo *addr; const struct addrinfo *addr;
int err; int err;
addr=*_addr; addr=*_addr;
for(;;){ for(;;){
/*Move to the next address of the requested type.*/ /*Move to the next address of the requested type.*/
...@@ -1887,36 +1901,30 @@ static int op_sock_connect_next(op_sock _fd, ...@@ -1887,36 +1901,30 @@ static int op_sock_connect_next(op_sock _fd,
/*The number of address families to try connecting to simultaneously.*/ /*The number of address families to try connecting to simultaneously.*/
# define OP_NPROTOS (2) # define OP_NPROTOS (2)
static int op_http_connect(OpusHTTPStream *_stream,OpusHTTPConn *_conn, static int op_http_connect_impl(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
struct addrinfo *_addrs,struct timeb *_start_time){ const struct addrinfo *_addrs,struct timeb *_start_time){
struct addrinfo *addr; const struct addrinfo *addr;
struct addrinfo *addrs[OP_NPROTOS]; const struct addrinfo *addrs[OP_NPROTOS];
struct pollfd fds[OP_NPROTOS]; struct pollfd fds[OP_NPROTOS];
int ai_family; int ai_family;
int nprotos; int nprotos;
int ret; int ret;
int pi; int pi;
int pj; int pj;
for(pi=0;pi<OP_NPROTOS;pi++)addrs[pi]=NULL; for(pi=0;pi<OP_NPROTOS;pi++)addrs[pi]=NULL;
addr=_addrs;
/*Try connecting via both IPv4 and IPv6 simultaneously, and keep the first /*Try connecting via both IPv4 and IPv6 simultaneously, and keep the first
one that succeeds.*/ one that succeeds.
for(;addr!=NULL;addr=addr->ai_next){ Start by finding the first address from each family.
/*Give IPv6 a slight edge by putting it first in the list.*/ We order the first connection attempts in the same order the address
if(addr->ai_family==AF_INET6){ families were returned in the DNS records in accordance with RFC 6555.*/
for(addr=_addrs,nprotos=0;addr!=NULL&&nprotos<OP_NPROTOS;addr=addr->ai_next){
if(addr->ai_family==AF_INET6||addr->ai_family==AF_INET){
OP_ASSERT(addr->ai_addrlen<=sizeof(struct sockaddr_in6)); OP_ASSERT(addr->ai_addrlen<=sizeof(struct sockaddr_in6));
if(addrs[0]==NULL)addrs[0]=addr;
}
else if(addr->ai_family==AF_INET){
OP_ASSERT(addr->ai_addrlen<=sizeof(struct sockaddr_in)); OP_ASSERT(addr->ai_addrlen<=sizeof(struct sockaddr_in));
if(addrs[1]==NULL)addrs[1]=addr; /*If we've seen this address family before, skip this address for now.*/
} for(pi=0;pi<nprotos;pi++)if(addrs[pi]->ai_family==addr->ai_family)break;
} if(pi<nprotos)continue;
/*Consolidate the list of addresses.*/ addrs[nprotos++]=addr;
for(pi=nprotos=0;pi<OP_NPROTOS;pi++){
if(addrs[pi]!=NULL){
addrs[nprotos]=addrs[pi];
nprotos++;
} }
} }
/*Pop the connection off the free list and put it on the LRU list.*/ /*Pop the connection off the free list and put it on the LRU list.*/
...@@ -1928,7 +1936,12 @@ static int op_http_connect(OpusHTTPStream *_stream,OpusHTTPConn *_conn, ...@@ -1928,7 +1936,12 @@ static int op_http_connect(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
*&_conn->read_time=*_start_time; *&_conn->read_time=*_start_time;
_conn->read_bytes=0; _conn->read_bytes=0;
_conn->read_rate=0; _conn->read_rate=0;
/*Try to start a connection to each protocol.*/ /*Try to start a connection to each protocol.
RFC 6555 says it is RECOMMENDED that connection attempts be paced
150...250 ms apart "to balance human factors against network load", but
that "stateful algorithms" (that's us) "are expected to be more
aggressive".
We are definitely more aggressive: we don't pace at all.*/
for(pi=0;pi<nprotos;pi++){ for(pi=0;pi<nprotos;pi++){
ai_family=addrs[pi]->ai_family; ai_family=addrs[pi]->ai_family;
fds[pi].fd=socket(ai_family,SOCK_STREAM,addrs[pi]->ai_protocol); fds[pi].fd=socket(ai_family,SOCK_STREAM,addrs[pi]->ai_protocol);
...@@ -2020,6 +2033,29 @@ static int op_http_connect(OpusHTTPStream *_stream,OpusHTTPConn *_conn, ...@@ -2020,6 +2033,29 @@ static int op_http_connect(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
return 0; return 0;
} }
static int op_http_connect(OpusHTTPStream *_stream,OpusHTTPConn *_conn,
const struct addrinfo *_addrs,struct timeb *_start_time){
struct timeb resolve_time;
struct addrinfo *new_addrs;
int ret;
/*Re-resolve the host if we need to (RFC 6555 says we MUST do so
occasionally).*/
new_addrs=NULL;
ftime(&resolve_time);
if(_addrs!=&_stream->addr_info||op_time_diff_ms(&resolve_time,
&_stream->resolve_time)>=OP_RESOLVE_CACHE_TIMEOUT_MS){
new_addrs=op_resolve(_stream->connect_host,_stream->connect_port);
if(OP_LIKELY(new_addrs!=NULL)){
_addrs=new_addrs;
*&_stream->resolve_time=*&resolve_time;
}
else if(OP_LIKELY(_addrs==NULL))return OP_FALSE;
}
ret=op_http_connect_impl(_stream,_conn,_addrs,_start_time);
if(new_addrs!=NULL)freeaddrinfo(new_addrs);
return ret;
}
# define OP_BASE64_LENGTH(_len) (((_len)+2)/3*4) # define OP_BASE64_LENGTH(_len) (((_len)+2)/3*4)
static const char BASE64_TABLE[64]={ static const char BASE64_TABLE[64]={
...@@ -2136,51 +2172,31 @@ static int op_http_stream_open(OpusHTTPStream *_stream,const char *_url, ...@@ -2136,51 +2172,31 @@ static int op_http_stream_open(OpusHTTPStream *_stream,const char *_url,
int _skip_certificate_check,const char *_proxy_host,unsigned _proxy_port, int _skip_certificate_check,const char *_proxy_host,unsigned _proxy_port,
const char *_proxy_user,const char *_proxy_pass,OpusServerInfo *_info){ const char *_proxy_user,const char *_proxy_pass,OpusServerInfo *_info){
struct addrinfo *addrs; struct addrinfo *addrs;
const char *last_host;
unsigned last_port;
int nredirs; int nredirs;
int ret; int ret;
if(_proxy_host!=NULL&&OP_UNLIKELY(_proxy_port>65535U))return OP_EINVAL;
last_host=NULL;
/*We shouldn't have to initialize last_port, but gcc is too dumb to figure
out that last_host!=NULL implies we've already taken one trip through the
loop.*/
last_port=0;
#if defined(_WIN32) #if defined(_WIN32)
op_init_winsock(); op_init_winsock();
#endif #endif
ret=op_parse_url(&_stream->url,_url); ret=op_parse_url(&_stream->url,_url);
if(OP_UNLIKELY(ret<0))return ret; if(OP_UNLIKELY(ret<0))return ret;
if(_proxy_host!=NULL){
if(OP_UNLIKELY(_proxy_port>65535U))return OP_EINVAL;
_stream->connect_host=op_string_dup(_proxy_host);
_stream->connect_port=_proxy_port;
}
else{
_stream->connect_host=_stream->url.host;
_stream->connect_port=_stream->url.port;
}
addrs=NULL;
for(nredirs=0;nredirs<OP_REDIRECT_LIMIT;nredirs++){ for(nredirs=0;nredirs<OP_REDIRECT_LIMIT;nredirs++){
struct timeb start_time; OpusParsedURL next_url;
struct timeb end_time; struct timeb start_time;
char *next; struct timeb end_time;
char *status_code; char *next;
const char *host; char *status_code;
unsigned port; int minor_version_pos;
int minor_version_pos; int v1_1_compat;
int v1_1_compat;
if(_proxy_host==NULL){
host=_stream->url.host;
port=_stream->url.port;
}
else{
host=_proxy_host;
port=_proxy_port;
}
/*If connecting to the same place as last time, don't re-resolve it.*/
addrs=NULL;
if(last_host!=NULL){
if(strcmp(last_host,host)==0&&last_port==port)addrs=&_stream->addr_info;
else if(_stream->ssl_session!=NULL){
/*Forget any cached SSL session from the last host.*/
SSL_SESSION_free(_stream->ssl_session);
_stream->ssl_session=NULL;
}
if(last_host!=_proxy_host)_ogg_free((void *)last_host);
}
last_host=host;
last_port=port;
/*Initialize the SSL library if necessary.*/ /*Initialize the SSL library if necessary.*/
if(OP_URL_IS_SSL(&_stream->url)&&_stream->ssl_ctx==NULL){ if(OP_URL_IS_SSL(&_stream->url)&&_stream->ssl_ctx==NULL){
SSL_CTX *ssl_ctx; SSL_CTX *ssl_ctx;
...@@ -2244,12 +2260,7 @@ static int op_http_stream_open(OpusHTTPStream *_stream,const char *_url, ...@@ -2244,12 +2260,7 @@ static int op_http_stream_open(OpusHTTPStream *_stream,const char *_url,
} }
} }
/*Actually make the connection.*/ /*Actually make the connection.*/
if(addrs!=&_stream->addr_info){
addrs=op_resolve(host,port);
if(OP_UNLIKELY(addrs==NULL))return OP_FALSE;
}
ret=op_http_connect(_stream,_stream->conns+0,addrs,&start_time); ret=op_http_connect(_stream,_stream->conns+0,addrs,&start_time);
if(addrs!=&_stream->addr_info)freeaddrinfo(addrs);
if(OP_UNLIKELY(ret<0))return ret; if(OP_UNLIKELY(ret<0))return ret;
/*Build the request to send.*/ /*Build the request to send.*/
_stream->request.nbuf=0; _stream->request.nbuf=0;
...@@ -2524,15 +2535,33 @@ static int op_http_stream_open(OpusHTTPStream *_stream,const char *_url, ...@@ -2524,15 +2535,33 @@ static int op_http_stream_open(OpusHTTPStream *_stream,const char *_url,
if(strcmp(header,"location")==0&&OP_LIKELY(_url==NULL))_url=cdr; if(strcmp(header,"location")==0&&OP_LIKELY(_url==NULL))_url=cdr;
} }
if(OP_UNLIKELY(_url==NULL))return OP_FALSE; if(OP_UNLIKELY(_url==NULL))return OP_FALSE;
/*Don't free last_host if it came from the last URL.*/ ret=op_parse_url(&next_url,_url);
if(last_host!=_proxy_host)_stream->url.host=NULL; if(OP_UNLIKELY(ret<0))return ret;
op_parsed_url_clear(&_stream->url); if(_proxy_host==NULL||_stream->ssl_session!=NULL){
ret=op_parse_url(&_stream->url,_url); if(strcmp(_stream->url.host,next_url.host)==0
if(OP_UNLIKELY(ret<0)){ &&_stream->url.port==next_url.port){
if(ret==OP_EINVAL)ret=OP_FALSE; /*Try to skip re-resolve when connecting to the same host.*/
if(last_host!=_proxy_host)_ogg_free((void *)last_host); addrs=&_stream->addr_info;
return ret; }
else{
if(_stream->ssl_session!=NULL){
/*Forget any cached SSL session from the last host.*/
SSL_SESSION_free(_stream->ssl_session);
_stream->ssl_session=NULL;
}
}
}
if(_proxy_host==NULL){
OP_ASSERT(_stream->connect_host==_stream->url.host);
_stream->connect_host=next_url.host;
_stream->connect_port=next_url.port;
} }
/*Always try to skip re-resolve for proxy connections.*/
else addrs=&_stream->addr_info;
op_parsed_url_clear(&_stream->url);
*&_stream->url=*&next_url;
/*TODO: On servers/proxies that support pipelining, we might be able to
re-use this connection.*/
op_http_conn_close(_stream,_stream->conns+0,&_stream->lru_head,1); op_http_conn_close(_stream,_stream->conns+0,&_stream->lru_head,1);
} }
/*Redirection limit reached.*/ /*Redirection limit reached.*/
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment