Permalink
Browse files

Merge pull request #175 from yakaz/fcgi-ipv6

IPv6 address parsing + IPv6 lookup for FastCGI and reverse proxy
  • Loading branch information...
2 parents 3bd2cbf + 53fdbfe commit 1ff49f3413e3fc1ae407c072d3d77498f4966b16 @vinoski vinoski committed Apr 16, 2014
Showing with 261 additions and 31 deletions.
  1. +15 −2 doc/yaws.tex
  2. +2 −1 include/yaws.hrl
  3. +17 −2 man/yaws.conf.5
  4. +114 −2 src/yaws.erl
  5. +14 −9 src/yaws_cgi.erl
  6. +78 −8 src/yaws_config.erl
  7. +3 −7 src/yaws_revproxy.erl
  8. +9 −0 test/conf/revproxyconf.conf
  9. +9 −0 test/t4/app_test.erl
View
@@ -2648,8 +2648,9 @@ \section{Server Part}
\item \verb+fcgi_app_server = Host:Port+ ---
The hostname and TCP port number of a FastCGI application
- server. The TCP port number is not optional. There is no default
- value.
+ server. To specify an IPv6 address, put it inside square
+ brackets (ex: "[::1]:9000"). The TCP port number is not
+ optional. There is no default value.
\item \verb+fcgi_trace_protocol = true | false+ ---
Enable or disable tracing of FastCGI protocol messages as info log
@@ -3054,6 +3055,11 @@ \section{Server Part}
\begin{verbatim}
php_handler = <fcgi, 127.0.0.1:54321>
\end{verbatim}
+ If you need to specify an IPv6 address, use square
+ brackets:
+\begin{verbatim}
+ php_handler = <fcgi, [::1]:54321>
+\end{verbatim}
The PHP interpreter needs read access to the files it
is to serve. Thus, if you run it in a different
security context than \Yaws\ itself, make sure it has
@@ -3112,6 +3118,13 @@ \section{Server Part}
in a server part using this directive, then it is replaced. Else
it is kept.
+\item \verb+nslookup_pref = [inet | inet6]+ ---
+ For fcgi servers and revproxy URLs, define the name
+ resolution preference. For example, to perform only IPv4
+ name resolution, use \textit{[inet]}. To do both IPv4
+ and IPv6 but try IPv6 first, use \textit{[inet6, inet]}.
+ Default value is \textit{[inet]}.
+
\item \verb+<ssl> .... </ssl>+
This begins and ends an SSL configuration for this server. It's
possible to virthost several SSL servers on the same IP given
View
@@ -109,7 +109,8 @@
ysession_mod = yaws_session_server, % storage module for ysession
acceptor_pool_size = 8, % size of acceptor proc pool
- mime_types_info % undefined | #mime_types_info{}
+ mime_types_info, % undefined | #mime_types_info{}
+ nslookup_pref = [inet] % [inet | inet6]
}).
-record(ssl, {
View
@@ -500,8 +500,10 @@ Turns on/off statistics gathering for a virtual server. Default is \fIfalse\fR.
.TP
\fBfcgi_app_server = Host:Port\fR
-The hostname and TCP port number of a FastCGI application server. The TCP port
-number is not optional. There is no default value.
+The hostname and TCP port number of a FastCGI application server.
+To specify an IPv6 address, put it inside square brackets (ex:
+"[::1]:9000"). The TCP port number is not optional. There is no default
+value.
.TP
\fBfcgi_trace_protocol = true | false\fR
@@ -915,6 +917,12 @@ make use of this PHP server from Yaws, specify:
php_handler = <fcgi, 127.0.0.1:54321>
.fi
+If you need to specify an IPv6 address, use square brackets:
+
+.nf
+ php_handler = <fcgi, [::1]:54321>
+.fi
+
The PHP interpreter needs read access to the files it is to serve. Thus, if you
run it in a different security context than Yaws itself, make sure it has access
to the .php files.
@@ -974,6 +982,13 @@ mapping is defined in the global part and redefined in a server part using this
directive, then it is replaced. Else it is kept.
.TP
+\fBnslookup_pref = [inet | inet6]\fR
+For fcgi servers and revproxy URLs, define the name resolution
+preference. For example, to perform only IPv4 name resolution, use
+[inet]. To do both IPv4 and IPv6 but try IPv6 first, use [inet6, inet].
+Default value is [inet].
+
+.TP
\fB<ssl> ... </ssl>\fR
This begins and ends an SSL configuration for this server. It's possible to
virthost several SSL servers on the same IP given that they all share the same
View
@@ -27,7 +27,8 @@
gconf_mnesia_dir/1, gconf_log_wrap_size/1, gconf_cache_refresh_secs/1,
gconf_include_dir/1, gconf_phpexe/1, gconf_yaws/1, gconf_id/1,
gconf_enable_soap/1, gconf_soap_srv_mods/1, gconf_ysession_mod/1,
- gconf_acceptor_pool_size/1, gconf_mime_types_info/1]).
+ gconf_acceptor_pool_size/1, gconf_mime_types_info/1,
+ gconf_nslookup_pref/1]).
-export([sconf_port/1, sconf_flags/1, sconf_redirect_map/1, sconf_rhost/1,
sconf_rmethod/1, sconf_docroot/1, sconf_xtra_docroots/1,
@@ -147,6 +148,8 @@
exists/1,
mkdir/1]).
+-export([tcp_connect/3, tcp_connect/4, ssl_connect/3, ssl_connect/4]).
+
-export([do_recv/3, do_recv/4, cli_recv/3,
gen_tcp_send/2,
http_get_headers/2]).
@@ -251,6 +254,7 @@ gconf_soap_srv_mods (#gconf{soap_srv_mods = X}) -> X.
gconf_ysession_mod (#gconf{ysession_mod = X}) -> X.
gconf_acceptor_pool_size (#gconf{acceptor_pool_size = X}) -> X.
gconf_mime_types_info (#gconf{mime_types_info = X}) -> X.
+gconf_nslookup_pref (#gconf{nslookup_pref = X}) -> X.
sconf_port (#sconf{port = X}) -> X.
@@ -530,7 +534,9 @@ setup_gconf(GL, GC) ->
GC#gconf.acceptor_pool_size),
mime_types_info = setup_mime_types_info(
GL, GC#gconf.mime_types_info
- )
+ ),
+ nslookup_pref = lkup(nslookup_pref, GL,
+ GC#gconf.nslookup_pref)
}.
set_gc_flags([{tty_trace, Bool}|T], Flags) ->
@@ -1986,6 +1992,112 @@ ensure_exist(Path) ->
end
end.
+%%
+%%
+%% TCP/SSL connection with a configurable IPv4/IPv6 preference on NS lookup.
+%%
+%%
+
+tcp_connect(Host, Port, Options) ->
+ tcp_connect(Host, Port, Options, infinity).
+
+tcp_connect(Host, Port, Options, Timeout) ->
+ parse_ipaddr_and_connect(tcp, Host, Port, Options, Timeout).
+
+ssl_connect(Host, Port, Options) ->
+ ssl_connect(Host, Port, Options, infinity).
+
+ssl_connect(Host, Port, Options, Timeout) ->
+ parse_ipaddr_and_connect(ssl, Host, Port, Options, Timeout).
+
+parse_ipaddr_and_connect(Proto, IP, Port, Options, Timeout)
+when is_tuple(IP) ->
+ %% The caller handled name resolution himself.
+ filter_tcpoptions_and_connect(Proto, undefined,
+ IP, Port, Options, Timeout);
+parse_ipaddr_and_connect(Proto, [$[ | Rest], Port, Options, Timeout) ->
+ %% yaws_api:parse_url/1 keep the "[...]" enclosing an IPv6 address.
+ %% Remove them now, and parse the address.
+ IP = string:strip(Rest, right, $]),
+ parse_ipaddr_and_connect(Proto, IP, Port, Options, Timeout);
+parse_ipaddr_and_connect(Proto, Host, Port, Options, Timeout) ->
+ %% First, try to parse an IP address, because inet:getaddr/2 could
+ %% return nxdomain if the family doesn't match the IP address
+ %% format.
+ case inet:parse_strict_address(Host) of
+ {ok, IP} ->
+ filter_tcpoptions_and_connect(Proto, undefined,
+ IP, Port, Options, Timeout);
+ {error, einval} ->
+ NsLookupPref = get_nslookup_pref(Options),
+ filter_tcpoptions_and_connect(Proto, NsLookupPref,
+ Host, Port, Options, Timeout)
+ end.
+
+filter_tcpoptions_and_connect(Proto, NsLookupPref,
+ Host, Port, Options, Timeout) ->
+ %% Now that we have IP addresses, remove family from the TCP options,
+ %% because calling gen_tcp:connect/3 with {127,0,0,1} and [inet6]
+ %% would return {error, nxdomain otherwise}.
+ OptionsWithoutFamily = lists:filter(fun
+ (inet) -> false;
+ (inet6) -> false;
+ (_) -> true
+ end, Options),
+ resolve_and_connect(Proto, NsLookupPref, Host, Port, OptionsWithoutFamily, Timeout).
+
+resolve_and_connect(Proto, _, IP, Port, Options, Timeout)
+when is_tuple(IP) ->
+ do_connect(Proto, IP, Port, Options, Timeout);
+resolve_and_connect(Proto, [Family | Rest], Host, Port, Options, Timeout) ->
+ Result = case inet:getaddr(Host, Family) of
+ {ok, IP} -> do_connect(Proto, IP, Port, Options, Timeout);
+ R -> R
+ end,
+ case Result of
+ {ok, Socket} ->
+ {ok, Socket};
+ {error, _} when length(Rest) >= 1 ->
+ %% If the connection fails here, ignore the error and
+ %% continue with the next address family.
+ resolve_and_connect(Proto, Rest, Host, Port, Options, Timeout);
+ {error, Reason} ->
+ %% This was the last IP address in the list, return the
+ %% connection error.
+ {error, Reason}
+ end.
+
+do_connect(Proto, IP, Port, Options, Timeout) ->
+ case Proto of
+ tcp -> gen_tcp:connect(IP, Port, Options, Timeout);
+ ssl -> ssl:connect(IP, Port, Options, Timeout)
+ end.
+
+%% If the caller specified inet or inet6 in the TCP options, prefer
+%% this to the global nslookup_pref parameter.
+%%
+%% This can be used in processes which can't use get(gc) to get the
+%% global conf: if they are given the global conf, they can get
+%% nslookup_pref value and add it the TCP options.
+%%
+%% If neither TCP options specify the family, nor the global conf is
+%% accessible, use default value declared in #gconf definition.
+get_nslookup_pref(TcpOptions) ->
+ get_nslookup_pref(TcpOptions, []).
+
+get_nslookup_pref([inet | Rest], Result) ->
+ get_nslookup_pref(Rest, [inet | Result]);
+get_nslookup_pref([inet6 | Rest], Result) ->
+ get_nslookup_pref(Rest, [inet6 | Result]);
+get_nslookup_pref([_ | Rest], Result) ->
+ get_nslookup_pref(Rest, Result);
+get_nslookup_pref([], []) ->
+ case get(gc) of
+ undefined -> gconf_nslookup_pref(#gconf{});
+ GC -> gconf_nslookup_pref(GC)
+ end;
+get_nslookup_pref([], Result) ->
+ lists:reverse(Result).
%%
%%
View
@@ -21,7 +21,7 @@
%%% TODO: Implement FastCGI filter role.
--export([cgi_worker/7, fcgi_worker/5]).
+-export([cgi_worker/7, fcgi_worker/6]).
%%%=====================================================================
%%% Code shared between CGI and FastCGI
@@ -802,8 +802,10 @@ call_fcgi(Role, Arg, Options) ->
[Role, fcgi_role_name(Role),
Options,
Arg]),
+ GlobalConf = get(gc),
ServerConf = get(sc),
- WorkerPid = fcgi_start_worker(Role, Arg, ServerConf, Options),
+ WorkerPid = fcgi_start_worker(Role, Arg, GlobalConf, ServerConf,
+ Options),
handle_clidata(Arg, WorkerPid)
end.
@@ -829,12 +831,12 @@ fcgi_worker_fail_if(true, WorkerState, Reason) ->
fcgi_worker_fail_if(_Condition, _WorkerState, _Reason) ->
ok.
-fcgi_start_worker(Role, Arg, ServerConf, Options) ->
+fcgi_start_worker(Role, Arg, GlobalConf, ServerConf, Options) ->
proc_lib:spawn(?MODULE, fcgi_worker,
- [self(), Role, Arg, ServerConf, Options]).
+ [self(), Role, Arg, GlobalConf, ServerConf, Options]).
-fcgi_worker(ParentPid, Role, Arg, ServerConf, Options) ->
+fcgi_worker(ParentPid, Role, Arg, GlobalConf, ServerConf, Options) ->
{DefaultSvrHost, DefaultSvrPort} =
case ServerConf#sconf.fcgi_app_server of
undefined ->
@@ -857,9 +859,11 @@ fcgi_worker(ParentPid, Role, Arg, ServerConf, Options) ->
?sc_fcgi_trace_protocol(ServerConf)),
LogAppError = get_opt(log_app_error, Options,
?sc_fcgi_log_app_error(ServerConf)),
+ TcpOptions = yaws:gconf_nslookup_pref(GlobalConf),
AppServerSocket =
fcgi_connect_to_application_server(PreliminaryWorkerState,
- AppServerHost, AppServerPort),
+ AppServerHost, AppServerPort,
+ TcpOptions),
?Debug("Start FastCGI worker:~n"
" Role = ~p (~s)~n"
" AppServerHost = ~p~n"
@@ -910,9 +914,10 @@ fcgi_pass_through_client_data(WorkerState) ->
end.
-fcgi_connect_to_application_server(WorkerState, Host, Port) ->
- Options = [binary, {packet, 0}, {active, false}, {nodelay, true}],
- case gen_tcp:connect(Host, Port, Options, ?FCGI_CONNECT_TIMEOUT_MSECS) of
+fcgi_connect_to_application_server(WorkerState, Host, Port, TcpOptions) ->
+ Options = [binary, {packet, 0}, {active, false}, {nodelay, true} |
+ TcpOptions],
+ case yaws:tcp_connect(Host, Port, Options, ?FCGI_CONNECT_TIMEOUT_MSECS) of
{error, Reason} ->
fcgi_worker_fail(WorkerState,
{"connect to application server failed", Reason});
Oops, something went wrong.

0 comments on commit 1ff49f3

Please sign in to comment.