Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add a nslookup_pref global parameter

This parameter allows to change the name resolution and connection
preference for fcgi servers and revproxy URLs. It takes a list of the
form [inet | inet6].

For instance, to perform only IPv4 resolution:
    nslookup_pref = [inet]

To perform both IPv4 and IPv6, but try IPv6 first:
    nslookup_pref = [inet6, inet]

The default value is [inet]. Therefore, the behavior remains the same
with this addition.

This parameter is used by two new functions:
    o  yaws:tcp_connect/{3,4}
    o  yaws:ssl_connect/{3,4}

They are wrappers around gen_tcp:connect/{3,4} and ssl:connect/{3,4} and
take care of connection retries for each configured family.
  • Loading branch information...
commit 53fdbfed12648942324400074b0b7bcb5def8d2c 1 parent 2e662ff
@dumbbell dumbbell authored
View
7 doc/yaws.tex
@@ -3118,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
3  include/yaws.hrl
@@ -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
7 man/yaws.conf.5
@@ -982,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
116 src/yaws.erl
@@ -26,7 +26,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,
@@ -146,6 +147,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]).
@@ -239,6 +242,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.
@@ -518,7 +522,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) ->
@@ -1974,6 +1980,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
23 src/yaws_cgi.erl
@@ -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});
View
47 src/yaws_config.erl
@@ -1130,6 +1130,14 @@ fload(FD, globals, GC, C, Cs, Lno, Chars) ->
{error, Str} ->
{error, ?F("~s at line ~w", [Str, Lno])}
end;
+ ["nslookup_pref", '=' | Pref] ->
+ case parse_nslookup_pref(Pref) of
+ {ok, Families} ->
+ fload(FD, globals, GC#gconf{nslookup_pref = Families},
+ C, Cs, Lno+1, Next);
+ {error, Str} ->
+ {error, ?F("~s at line ~w", [Str, Lno])}
+ end;
['<', "server", Server, '>'] -> %% first server
@@ -2597,6 +2605,45 @@ parse_mime_types_info(add_charsets, NewCharsets, Info) ->
end.
+parse_nslookup_pref(Pref) ->
+ parse_nslookup_pref(Pref, []).
+
+parse_nslookup_pref(Empty, []) when Empty == [] orelse Empty == ['[', ']'] ->
+ %% Get default value, if nslookup_pref = [].
+ {ok, yaws:gconf_nslookup_pref(#gconf{})};
+parse_nslookup_pref([C, Family | Rest], Result)
+ when C == '[' orelse C == ',' ->
+ case Family of
+ "inet" ->
+ case lists:member(inet, Result) of
+ false -> parse_nslookup_pref(Rest, [inet | Result]);
+ true -> parse_nslookup_pref(Rest, Result)
+ end;
+ "inet6" ->
+ case lists:member(inet6, Result) of
+ false -> parse_nslookup_pref(Rest, [inet6 | Result]);
+ true -> parse_nslookup_pref(Rest, Result)
+ end;
+ _ ->
+ case Result of
+ [PreviousFamily | _] ->
+ {error, ?F("Invalid nslookup_pref: invalid family or "
+ "token '~s', after family '~s'",
+ [Family, PreviousFamily])};
+ [] ->
+ {error, ?F("Invalid nslookup_pref: invalid family or "
+ "token '~s'", [Family])}
+ end
+ end;
+parse_nslookup_pref([']'], Result) ->
+ {ok, lists:reverse(Result)};
+parse_nslookup_pref([Invalid | _], []) ->
+ {error, ?F("Invalid nslookup_pref: unexpected token '~s'", [Invalid])};
+parse_nslookup_pref([Invalid | _], [Family | _]) ->
+ {error, ?F("Invalid nslookup_pref: unexpected token '~s', "
+ "after family '~s'", [Invalid, Family])}.
+
+
parse_redirect(Path, [Code, URL], Mode, Lno) ->
case catch list_to_integer(Code) of
I when is_integer(I), I >= 300, I =< 399 ->
View
10 src/yaws_revproxy.erl
@@ -574,24 +574,20 @@ connect(URL) ->
end.
do_connect(URL) ->
- InetType = if
- is_tuple(URL#url.host), size(URL#url.host) == 8 -> [inet6];
- true -> []
- end,
Opts = [
binary,
{packet, raw},
{active, false},
{recbuf, 8192},
{reuseaddr, true}
- ] ++ InetType,
+ ],
case URL#url.scheme of
http ->
Port = case URL#url.port of
undefined -> 80;
P -> P
end,
- case gen_tcp:connect(URL#url.host, Port, Opts) of
+ case yaws:tcp_connect(URL#url.host, Port, Opts) of
{ok, S} -> {ok, S, nossl};
Err -> Err
end;
@@ -600,7 +596,7 @@ do_connect(URL) ->
undefined -> 443;
P -> P
end,
- case ssl:connect(URL#url.host, Port, Opts) of
+ case yaws:ssl_connect(URL#url.host, Port, Opts) of
{ok, S} -> {ok, S, ssl};
Err -> Err
end;
View
9 test/conf/revproxyconf.conf
@@ -110,6 +110,7 @@ use_fdsrv = false
arg_rewrite_mod = rewritetest
revproxy = /revproxy1 http://localhost:8001
revproxy = /revproxy2 http://localhost:8002
+ revproxy = /revproxy3 http://[::1]:8006
</server>
<server localhost>
@@ -157,3 +158,11 @@ use_fdsrv = false
revproxy = /revproxy2 http://localhost:8002 intercept_mod intercept2
revproxy = /revproxy3 http://localhost:8002 intercept_mod intercept3
</server>
+
+<server localhost>
+ port = 8006
+ listen = ::
+ listen_backlog = 512
+ deflate = false
+ docroot = %YTOP%/test/t4/www1
+</server>
View
9 test/t4/app_test.erl
@@ -33,6 +33,7 @@ start() ->
test_failed_resp_interception_revproxy(),
test_good_interception_revproxy(),
test_fwdproxy(),
+ test_ipv6_address(),
ok
catch
Error:Reason ->
@@ -257,6 +258,14 @@ test_fwdproxy() ->
?line Res = Body2,
ok.
+test_ipv6_address() ->
+ io:format("revproxy_url_ipv6\n", []),
+ Uri = "http://localhost:8000/revproxy3/hello.txt",
+ Res = "Hello, World!\n",
+
+ ?line {ok, "200", _, _} = ibrowse:send_req(Uri, [], get),
+ ok.
+
recv_hdrs(Sock) ->
inet:setopts(Sock, [{packet, http}]),
recv_hdrs(Sock, 0).
Please sign in to comment.
Something went wrong with that request. Please try again.