Skip to content

Commit 20be95c

Browse files
committed
Merge pull request #1898 from UncleGrumpy/fix_socket_hostname
Align some gen_tcp and inet funcionality and error returns with OTP Closes #1839 These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents f8bdc7d + 4089b98 commit 20be95c

File tree

6 files changed

+285
-14
lines changed

6 files changed

+285
-14
lines changed

libs/estdlib/src/gen_tcp_inet.erl

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,27 @@
5151
Options :: [connect_option()]
5252
) ->
5353
{ok, Socket :: inet:socket()} | {error, Reason :: reason()}.
54-
connect(Address, Port, Params0) ->
54+
connect({_A, _B, _C, _D} = Address, Port, Params0) ->
55+
Address0 =
56+
case is_valid_ip_range(Address) of
57+
true ->
58+
Address;
59+
false ->
60+
error(badarg)
61+
end,
62+
Socket = open_port({spawn, "socket"}, []),
63+
Params = merge(Params0, ?DEFAULT_PARAMS),
64+
connect(Socket, normalize_address(Address0), Port, Params);
65+
connect(Address, Port, Params0) when is_atom(Address) ->
66+
Socket = open_port({spawn, "socket"}, []),
67+
Params = merge(Params0, ?DEFAULT_PARAMS),
68+
connect(Socket, normalize_address(Address), Port, Params);
69+
connect(Address, Port, Params0) when is_list(Address) ->
5570
Socket = open_port({spawn, "socket"}, []),
5671
Params = merge(Params0, ?DEFAULT_PARAMS),
57-
connect(Socket, normalize_address(Address), Port, Params).
72+
connect(Socket, Address, Port, Params);
73+
connect(_Address, _Port, _Params) ->
74+
error(badarg).
5875

5976
%% @hidden
6077
-spec send(Socket :: inet:socket(), Packet :: packet()) -> ok | {error, Reason :: reason()}.
@@ -161,6 +178,8 @@ connect(DriverPid, Address, Port, Params) ->
161178
case call(DriverPid, {init, InitParams}) of
162179
ok ->
163180
{ok, DriverPid};
181+
{error, {getaddrinfo, _Err}} ->
182+
{error, nxdomain};
164183
ErrorReason ->
165184
%% TODO close port
166185
ErrorReason
@@ -207,6 +226,17 @@ normalize_address({A, B, C, D}) when
207226

208227
%% TODO IPv6
209228

229+
%% @private
230+
is_valid_ip_range({A, B, C, D}) when
231+
is_integer(A) andalso A >= 0 andalso A < 256 andalso
232+
is_integer(B) andalso B >= 0 andalso B < 256 andalso
233+
is_integer(C) andalso C >= 0 andalso C < 256 andalso
234+
is_integer(D) andalso D >= 0 andalso D < 256
235+
->
236+
true;
237+
is_valid_ip_range(_Address) ->
238+
false.
239+
210240
%%
211241
%% Internal operations
212242
%%

libs/estdlib/src/gen_tcp_socket.erl

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,25 @@ connect(Address, Port, Options) ->
7272
ControllingProcess = self(),
7373
case socket:open(inet, stream, tcp) of
7474
{ok, Socket} ->
75-
case socket:connect(Socket, #{family => inet, addr => Address, port => Port}) of
76-
ok ->
77-
EffectiveOptions = maps:merge(?DEFAULT_OPTIONS, proplist_to_map(Options)),
78-
gen_server:start_link(
79-
?MODULE, {Socket, ControllingProcess, connect, EffectiveOptions}, []
80-
);
81-
ConnectError ->
82-
ConnectError
75+
case inet:getaddr(Address, inet) of
76+
{ok, Address0} ->
77+
case
78+
socket:connect(Socket, #{family => inet, addr => Address0, port => Port})
79+
of
80+
ok ->
81+
EffectiveOptions = maps:merge(
82+
?DEFAULT_OPTIONS, proplist_to_map(Options)
83+
),
84+
gen_server:start_link(
85+
?MODULE, {Socket, ControllingProcess, connect, EffectiveOptions}, []
86+
);
87+
ConnectError ->
88+
socket:close(Socket),
89+
ConnectError
90+
end;
91+
ResolveError ->
92+
socket:close(Socket),
93+
ResolveError
8394
end;
8495
OpenError ->
8596
OpenError

libs/estdlib/src/inet.erl

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,16 @@ peername({?GEN_TCP_MONIKER, Socket, Module}) ->
9696
%%-----------------------------------------------------------------------------
9797
-spec getaddr(Name :: ip_address() | hostname(), Family :: address_family()) ->
9898
{ok, ip_address()} | {error, Reason :: term()}.
99-
getaddr(Name, _Family) when is_tuple(Name) ->
99+
getaddr({A, B, C, D} = Name, _Family) when
100+
is_integer(A) andalso A >= 0 andalso A < 256 andalso
101+
is_integer(B) andalso B >= 0 andalso B < 256 andalso
102+
is_integer(C) andalso C >= 0 andalso C < 256 andalso
103+
is_integer(D) andalso D >= 0 andalso D < 256
104+
->
100105
{ok, Name};
101106
getaddr(Name, Family) when is_atom(Name) ->
102107
getaddr(atom_to_list(Name), Family);
103-
getaddr(Name, Family) ->
108+
getaddr(Name, Family) when is_list(Name) ->
104109
try net:getaddrinfo(Name) of
105110
{ok, Results} ->
106111
Filtered = [Addr || #{family := F, addr := #{addr := Addr}} <- Results, F =:= Family],
@@ -131,9 +136,13 @@ getaddr(Name, Family) ->
131136
_ ->
132137
{error, nxdomain}
133138
end;
139+
{error, -5} ->
140+
{error, nxdomain};
134141
{error, _} = Err ->
135142
Err
136143
catch
137144
error:function_clause ->
138145
{error, einval}
139-
end.
146+
end;
147+
getaddr(_Name, _Family) ->
148+
{error, einval}.

tests/libs/eavmlib/test_ahttp_client.erl

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,14 @@
2323

2424
test() ->
2525
ok = test_passive(),
26-
ok = test_active().
26+
ok = test_active(),
27+
case get_otp_version() of
28+
Version when Version =:= atomvm orelse (is_integer(Version) andalso Version >= 24) ->
29+
ok = test_passive_socket(),
30+
ok = test_active_socket();
31+
_ ->
32+
ok
33+
end.
2734

2835
test_passive() ->
2936
ok = ssl:start(),
@@ -62,6 +69,44 @@ test_active() ->
6269
ConnectError
6370
end.
6471

72+
test_passive_socket() ->
73+
ConnectResult = ahttp_client:connect(http, "test.atomvm.org", 80, [
74+
{inet_backend, socket},
75+
{active, false},
76+
{parse_headers, [<<"Location">>]}
77+
]),
78+
case ConnectResult of
79+
{ok, Conn} ->
80+
case ahttp_client:request(Conn, <<"GET">>, <<"/">>, [], undefined) of
81+
{ok, Conn2, _Ref} ->
82+
loop_passive(Conn2, #{});
83+
{error, _} = RequestError ->
84+
io:format("Request failed: ~p~n", [RequestError]),
85+
RequestError
86+
end;
87+
{error, _} = ConnectError ->
88+
io:format("Request failed: ~p~n", [ConnectError]),
89+
ConnectError
90+
end.
91+
92+
test_active_socket() ->
93+
ConnectResult = ahttp_client:connect(http, "test.atomvm.org", 80, [
94+
{inet_backend, socket}, {active, true}
95+
]),
96+
case ConnectResult of
97+
{ok, Conn} ->
98+
case ahttp_client:request(Conn, <<"GET">>, <<"/">>, [], undefined) of
99+
{ok, Conn2, _Ref} ->
100+
loop_active(Conn2, #{});
101+
{error, _} = RequestError ->
102+
io:format("Request failed: ~p~n", [RequestError]),
103+
RequestError
104+
end;
105+
{error, _} = ConnectError ->
106+
io:format("Request failed: ~p~n", [ConnectError]),
107+
ConnectError
108+
end.
109+
65110
loop_active(Conn, Resp) ->
66111
receive
67112
Message ->
@@ -117,3 +162,11 @@ parse_responses(
117162
_Expected
118163
) ->
119164
Resp#{done => true}.
165+
166+
get_otp_version() ->
167+
case erlang:system_info(machine) of
168+
"BEAM" ->
169+
list_to_integer(erlang:system_info(otp_release));
170+
_ ->
171+
atomvm
172+
end.

tests/libs/estdlib/test_gen_tcp.erl

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ test() ->
2828
ok = test_echo_server(),
2929
ok = test_echo_server(true),
3030
ok = test_listen_connect_parameters(),
31+
ok = test_connect_parameters(),
32+
ok = test_connect_bad_address(),
3133
ok = test_tcp_double_close(),
3234
ok.
3335

@@ -348,6 +350,168 @@ test_listen_connect_parameters_server_loop(ListenMode, false = ListenActive, Soc
348350
{error, {unexpected_result, server, passive_receive, Other}}
349351
end.
350352

353+
test_connect_parameters() ->
354+
IP =
355+
case inet:getaddr("www.github.com", inet) of
356+
{ok, IPAddress} ->
357+
IPAddress;
358+
Error ->
359+
io:format(
360+
"Unable to resolve www.github.com, ~p; unable to complete connection tests.~n",
361+
[Error]
362+
),
363+
throw(Error)
364+
end,
365+
Hostname = "www.atomvm.org",
366+
Port = 80,
367+
OptTests =
368+
case get_otp_version() of
369+
Version when Version =:= atomvm orelse (is_integer(Version) andalso Version >= 24) ->
370+
[
371+
[{active, true}],
372+
[{active, false}],
373+
[{inet_backend, socket}, {active, true}],
374+
[{inet_backend, socket}, {active, false}]
375+
];
376+
_ ->
377+
[
378+
[{active, true}],
379+
[{active, false}]
380+
]
381+
end,
382+
test_connect_parameters(OptTests, IP, Hostname, Port, []).
383+
384+
test_connect_parameters([], _IP, _Host, _Port, Results) ->
385+
case lists:flatten(Results) of
386+
[] ->
387+
ok;
388+
ErrorList ->
389+
ErrorList
390+
end;
391+
test_connect_parameters([Test | TestOpts], IP, Host, Port, Results) ->
392+
io:format("GEN_TCP_CONNECT-TEST> IP Address=~p (www.github.com) ClientConnectOptions=~p~n", [
393+
IP, Test
394+
]),
395+
IpResult =
396+
case gen_tcp:connect(IP, Port, Test) of
397+
{ok, Soc0} ->
398+
ok = gen_tcp:close(Soc0),
399+
[];
400+
IpErr ->
401+
{error, IpErr, {ip_connect, Test}}
402+
end,
403+
io:format("GEN_TCP_CONNECT-TEST> Hostname=~p ClientConnectOptions=~p~n", [Host, Test]),
404+
HostResult =
405+
case gen_tcp:connect(Host, Port, Test) of
406+
{ok, Soc1} ->
407+
ok = gen_tcp:close(Soc1),
408+
[];
409+
HostErr ->
410+
{error, HostErr, {hostname_connect, Test}}
411+
end,
412+
test_connect_parameters(TestOpts, IP, Host, Port, [Results, IpResult, HostResult]).
413+
414+
test_connect_bad_address() ->
415+
InetTest = test_connect_bad_address(inet_backend, []),
416+
SocketTest =
417+
case get_otp_version() of
418+
Version when Version =:= atomvm orelse (is_integer(Version) andalso Version >= 24) ->
419+
test_connect_bad_address(socket_backend, [{inet_backend, socket}]);
420+
_ ->
421+
ok
422+
end,
423+
Result = [InetTest, SocketTest],
424+
case Result of
425+
[ok, ok] ->
426+
ok;
427+
_ ->
428+
Result
429+
end.
430+
431+
test_connect_bad_address(Tag, Opts) ->
432+
T0 =
433+
try gen_tcp:connect({532, 0, 0, 1}, 80, Opts) of
434+
{ok, Port0} ->
435+
gen_tcp:close(Port0),
436+
{addr_bad_range_test, Tag, failed, invalid_ipaddr_accepted};
437+
{error, einval} ->
438+
case Tag of
439+
socket_backend ->
440+
ok;
441+
_ ->
442+
{addr_bad_range_test, Tag, unexpected, {error, einval}}
443+
end;
444+
Error0 ->
445+
{addr_bad_range_test, Tag, unexpected, Error0}
446+
catch
447+
_:badarg ->
448+
case Tag of
449+
inet_backend ->
450+
ok;
451+
_ ->
452+
{addr_bad_range_test, Tag, caught, badarg}
453+
end
454+
end,
455+
T1 =
456+
try gen_tcp:connect({10, 1}, 80, Opts) of
457+
{ok, Port1} ->
458+
gen_tcp:close(Port1),
459+
{addr_bad_tuple_test, Tag, error, invalid_ipaddr_accepted};
460+
{error, einval} ->
461+
case Tag of
462+
socket_backend ->
463+
ok;
464+
_ ->
465+
{addr_bad_tuple_test, Tag, unexpected, {error, einval}}
466+
end;
467+
Error1 ->
468+
{addr_bad_tuple_test, Tag, unexpected, Error1}
469+
catch
470+
_:badarg ->
471+
case Tag of
472+
inet_backend ->
473+
ok;
474+
_ ->
475+
{addr_bad_tuple_test, Tag, caught, badarg}
476+
end
477+
end,
478+
T2 =
479+
try gen_tcp:connect({127.0, 0, 0, 1.0}, 80, Opts) of
480+
{ok, Port2} ->
481+
gen_tcp:close(Port2),
482+
{addr_float_test, Tag, error, invalid_ipaddr_accepted};
483+
{error, einval} ->
484+
case Tag of
485+
socket_backend ->
486+
ok;
487+
_ ->
488+
{addr_float_test, Tag, unexpected, {error, einval}}
489+
end;
490+
Error2 ->
491+
{addr_float_test, Tag, unexpected, Error2}
492+
catch
493+
_:badarg ->
494+
case Tag of
495+
inet_backend ->
496+
ok;
497+
_ ->
498+
{addr_float_test, Tag, caught, badarg}
499+
end
500+
end,
501+
T3 =
502+
case gen_tcp:connect("foo.bar.flurtleblurb", 80, Opts) of
503+
{error, nxdomain} ->
504+
ok;
505+
Error3 ->
506+
{inet_unresolvable_test, Tag, unexpected, Error3}
507+
end,
508+
509+
Results = [T0, T1, T2, T3],
510+
if
511+
[ok, ok, ok, ok] =:= Results -> ok;
512+
true -> Results
513+
end.
514+
351515
test_tcp_double_close() ->
352516
{ok, Socket} = gen_tcp:listen(10543, [{active, false}]),
353517
ok = gen_tcp:close(Socket),

tests/libs/estdlib/test_inet.erl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ test_getaddr() ->
3333
% RFC8880
3434
{ok, {192, 0, 0, LastByte}} = inet:getaddr("ipv4only.arpa", inet),
3535
true = LastByte =:= 170 orelse LastByte =:= 171,
36+
{error, einval} = inet:getaddr(127, inet),
37+
{error, einval} = inet:getaddr({127.0, 0, 0, 1.0}, inet),
38+
{error, einval} = inet:getaddr({312, 0, 0, 1}, inet),
39+
{error, einval} = inet:getaddr({foo, bar}, inet),
3640
{error, einval} = inet:getaddr(<<"localhost">>, inet),
3741
{error, _} = inet:getaddr("localhost.invalid", inet),
3842
ok.

0 commit comments

Comments
 (0)