nickva commented on code in PR #5983:
URL: https://github.com/apache/couchdb/pull/5983#discussion_r3175385956
##########
src/couch_replicator/src/couch_replicator_auth_session.erl:
##########
@@ -311,11 +312,47 @@ refresh(#state{session_url = Url, user = User, pass =
Pass} = State) ->
{ok, string(), headers(), binary()} | {error, term()}.
http_request(#state{httpdb_pool = Pool} = State, Url, Headers, Method, Body) ->
Timeout = State#state.httpdb_timeout,
- Opts = [
+
+ % Apply DNS override using connect_to ibrowse option
+ {TargetHost, OriginalHost, Proto} =
+ case ibrowse_lib:parse_url(Url) of
+ {error, _} ->
+ {undefined, undefined, undefined};
+ #url{host = Host, protocol = Protocol} ->
+ {THost, OHost} = couch_replicator_dns:resolve_host(Host),
+ {THost, OHost, Protocol}
+ end,
+
+ Opts0 = [
{response_format, binary},
{inactivity_timeout, Timeout}
| State#state.httpdb_ibrowse_options
],
+
+ % Add connect_to ibrowse option if DNS override is active
+ Opts1 =
+ case OriginalHost of
+ undefined ->
+ Opts0;
+ _ ->
+ couch_log:debug("DNS override for session: ~s -> ~s",
[OriginalHost, TargetHost]),
+ [{connect_to, TargetHost} | Opts0]
+ end,
+
+ % Add SNI for HTTPS with DNS override
+ % SNI extension requires a hostname, not an IP address
+ Opts =
+ case {Proto, OriginalHost} of
+ {https, OrigHost} when is_list(OrigHost) ->
+ case inet:is_ip_address(OrigHost) of
Review Comment:
`is_ip_address/1` takes an already parsed tuple only
We could use `inet:parse_address/1`
```
> inet:parse_address(string:trim("[::0]", both, "[]")).
{ok,{0,0,0,0,0,0,0,0}}
> inet:parse_address("[::0]").
{error,einval}
> inet:parse_address("::0").
{ok,{0,0,0,0,0,0,0,0}}
> inet:parse_address("127").
{ok,{0,0,0,127}}
> inet:parse_address("1.2.3.4").
{ok,{1,2,3,4}}
>inet:parse_address("a.c.d.com").
{error,einval}
```
Also ibrowse_lib:parse_url{} also return a host type
```
> ibrowse_lib:parse_url("http://127.0.0.1").
#url{abspath = "http://127.0.0.1",host = "127.0.0.1",
port = 80,username = undefined,password = undefined,
path = "/",protocol = http,host_type = ipv4_address}
> ibrowse_lib:parse_url("http://[::0]").
#url{abspath = "http://[::0]",host = "::0",port = 80,
username = undefined,password = undefined,path = "/",
protocol = http,host_type = ipv6_address}
> ibrowse_lib:parse_url("http://foo.bar.baz.com").
#url{abspath = "http://foo.bar.baz.com",
host = "foo.bar.baz.com",port = 80,username = undefined,
password = undefined,path = "/",protocol = http,
host_type = hostname}
>
```
##########
src/couch_replicator/src/couch_replicator_dns.erl:
##########
@@ -0,0 +1,128 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_replicator_dns).
+
+-export([
+ resolve_host/1
+]).
+
+-ifdef(TEST).
+-export([
+ parse_config/1,
+ match_pattern/2,
+ get_overrides/0
+]).
+-endif.
+
+-type dns_override() :: {binary(), binary()}.
+
+-spec resolve_host(string()) -> {string(), string() | undefined}.
+resolve_host(Host) ->
+ case find_override(list_to_binary(Host), get_overrides()) of
+ {ok, Target} ->
+ {binary_to_list(Target), Host};
+ not_found ->
+ {Host, undefined}
+ end.
+
+-spec get_overrides() -> [dns_override()].
+get_overrides() ->
+ case config:get("replicator", "dns_overrides", undefined) of
+ undefined ->
+ [];
+ ConfigStr ->
+ parse_config(ConfigStr)
+ end.
+
+-spec parse_config(string()) -> [dns_override()].
+parse_config(ConfigStr) ->
+ ConfigBin = list_to_binary(ConfigStr),
+ Entries = binary:split(ConfigBin, <<",">>, [global, trim]),
+ lists:filtermap(fun parse_entry/1, Entries).
+
+% Note: IPv6 addresses in targets must be enclosed in brackets.
+% Format: pattern:target
+% Valid: *.example.com:[2001:db8::1]
+% Invalid: [2001:db8::1]:proxy.internal (IPv6 as pattern not supported)
+parse_entry(<<>>) ->
+ false;
+parse_entry(Entry0) ->
+ Entry = string:trim(Entry0),
+ case binary:split(Entry, <<":">>) of
+ [Pattern0, Target0] ->
+ Pattern = string:trim(Pattern0),
+ Target = string:trim(Target0),
+ case {Pattern, Target} of
+ {<<>>, _} ->
+ invalid_entry(Entry);
+ {_, <<>>} ->
+ invalid_entry(Entry);
+ % Reject IPv6 addresses as patterns (they start with '[')
+ {<<"[", _/binary>>, _} ->
+ invalid_entry_reason(Entry, "IPv6 addresses cannot be used
as patterns");
+ _ ->
+ {true, {Pattern, Target}}
Review Comment:
Would `*example:127.0.0.1` work or `*:127.0.0.1` work?
##########
src/couch_replicator/src/couch_replicator_dns.erl:
##########
@@ -0,0 +1,128 @@
+% Licensed under the Apache License, Version 2.0 (the "License"); you may not
+% use this file except in compliance with the License. You may obtain a copy of
+% the License at
+%
+% http://www.apache.org/licenses/LICENSE-2.0
+%
+% Unless required by applicable law or agreed to in writing, software
+% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+% License for the specific language governing permissions and limitations under
+% the License.
+
+-module(couch_replicator_dns).
+
+-export([
+ resolve_host/1
+]).
+
+-ifdef(TEST).
+-export([
+ parse_config/1,
+ match_pattern/2,
+ get_overrides/0
+]).
+-endif.
+
+-type dns_override() :: {binary(), binary()}.
+
+-spec resolve_host(string()) -> {string(), string() | undefined}.
+resolve_host(Host) ->
+ case find_override(list_to_binary(Host), get_overrides()) of
+ {ok, Target} ->
+ {binary_to_list(Target), Host};
+ not_found ->
+ {Host, undefined}
+ end.
+
+-spec get_overrides() -> [dns_override()].
+get_overrides() ->
+ case config:get("replicator", "dns_overrides", undefined) of
+ undefined ->
+ [];
+ ConfigStr ->
+ parse_config(ConfigStr)
+ end.
+
+-spec parse_config(string()) -> [dns_override()].
+parse_config(ConfigStr) ->
+ ConfigBin = list_to_binary(ConfigStr),
+ Entries = binary:split(ConfigBin, <<",">>, [global, trim]),
+ lists:filtermap(fun parse_entry/1, Entries).
+
+% Note: IPv6 addresses in targets must be enclosed in brackets.
+% Format: pattern:target
+% Valid: *.example.com:[2001:db8::1]
+% Invalid: [2001:db8::1]:proxy.internal (IPv6 as pattern not supported)
+parse_entry(<<>>) ->
+ false;
+parse_entry(Entry0) ->
+ Entry = string:trim(Entry0),
+ case binary:split(Entry, <<":">>) of
+ [Pattern0, Target0] ->
+ Pattern = string:trim(Pattern0),
+ Target = string:trim(Target0),
Review Comment:
If the IPv6 is passed in with brackets we should see if ibrowse knows how to
connect to a bracketed address. It may have to be stripped of brackets and/or
also parsed into an ipv6 address tuple
##########
src/couch_replicator/src/couch_replicator_httpc.erl:
##########
@@ -113,6 +114,10 @@ send_ibrowse_req(#httpdb{headers = BaseHeaders} = HttpDb0,
Params) ->
{Headers2, HttpDb} = couch_replicator_auth:update_headers(HttpDb0,
Headers1),
Url = full_url(HttpDb, Params),
Body = get_value(body, Params, []),
+
+ % Apply DNS override using connect_to ibrowse option
Review Comment:
Some of the "apply dns override" seems to be similar with that we do in
auth_session? Wonder if a helper function here work and call that from
auth_session, or some common utility library. If they diverge enough, it may
not work though.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]