Skip to content

Commit

Permalink
Add support for net:getaddrinfo/1,2
Browse files Browse the repository at this point in the history
Signed-off-by: Fred Dushin <[email protected]>
  • Loading branch information
fadushin committed Oct 29, 2023
1 parent ed578f2 commit a8e0f66
Show file tree
Hide file tree
Showing 19 changed files with 722 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added cmake configuration option `AVM_CONFIG_REBOOT_ON_NOT_OK` for STM32
- New gpio driver for STM32 with nif and port support for read and write functions.
- Added support for interrupts to STM32 GPIO port driver.
- Added support for `net:getaddrinfo/1,2`

## [0.6.0-alpha.1] - 2023-10-09

Expand Down
46 changes: 46 additions & 0 deletions doc/src/programmers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1860,3 +1860,49 @@ For example:
end

Close a UDP socket just as you would a TCP socket, as described above.

### Miscellaneous Networking APIs

You can retrieve information about hostnames and services using the `net:getaddrinfo/1` and `net:getaddrinfo/2` functions. The return value is a list of maps each of which contains address information about the host, including its family (`inet`), protocol (`tcp` or `udp`), type (`stream` or `dgram`), and the address, currently an IPv4 tuple.

> Note. Currently, the `net:getaddrinfo/1,2` functions only supports reporting of IPv4 addresses.
For example:

%% erlang
{ok, AddrInfos} = net:getaddrinfo("www.atomvm.net"),

lists:foreach(
fun(AddrInfo) ->
#{
family := Family,
protocol := Protocol,
type := Type,
address := Address
} = AddrInfo,

io:format("family: ~p prototcol: ~p type: ~p address: ~p", [Family, Protocol, Type, Address])

end,
AddrInfos
),

The `host` parameter can be a domain name (typically) or a dotted pair IPv4 address.

The returned map contains the network family (currently, only `inet` is supported), the protocol, type, and address of the host.

The address is itself a map, containing the family, port and IPv4 address of the requested host, e.g.,

#{family => inet, port => 0, addr => {192, 168, 212, 153}}

> Note. The [OTP documentation](https://www.erlang.org/doc/man/net#type-address_info) states that the address is returned under the `address` key in the address info map. However, OTP appears to use `addr` as the key. For compatibility with OTP 22 ff., AtomVM supports both the `address` and `addr` keys in this map (they reference the same inner map).
If you want to narrow the information you get back to a specific service type, you can specify a service name or port number (as a string value) as the second parameter:

%% erlang
{ok, AddrInfos} = net:getaddrinfo("www.atomvm.net", "https"),
...

Service names are well-known identifiers on the internet, but they may vary from operating system to operating system. See the `services(3)` man pages for more information.

> Note. Narrowing results via the service parameter is not supported on all platforms. In the case where it is not supported, AtomVM will resort to retrying the request without the service parameter.
1 change: 1 addition & 0 deletions libs/estdlib/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ set(ERLANG_MODULES
lists
maps
math
net
logger
logger_std_h
proplists
Expand Down
80 changes: 80 additions & 0 deletions libs/estdlib/src/net.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
%
% This file is part of AtomVM.
%
% Copyright 2023 Fred Dushin <[email protected]>
%
% 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.
%
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
%

-module(net).

-export([getaddrinfo/1, getaddrinfo/2]).

%% nif call (so we can use guards at the API)
-export([getaddrinfo_nif/2]).

-type addrinfo() :: #{
family := socket:domain(),
socktype := socket:type(),
protocol := socket:protocol(),
%% NB. The erlang docs appear to be wrong about this
address := socket:sockaddr(),
addr := socket:sockaddr()
}.

-type service() :: string().

-export_type([addrinfo/0, service/0]).

%%-----------------------------------------------------------------------------
%% @param Host the host string for which to find address information
%% @returns Address info for the specified host
%% @equiv getaddrinfo(Host, undefined)
%% @doc Retrieve address information for a given hostname.
%% @end
%%-----------------------------------------------------------------------------
-spec getaddrinfo(Host :: string()) -> {ok, AddrInfo :: addrinfo()} | {error, Reason :: term()}.
getaddrinfo(Host) when is_list(Host) ->
?MODULE:getaddrinfo(Host, undefined).

%%-----------------------------------------------------------------------------
%% @param Host the host string for which to find address information
%% @param Service the service string for which to find address information
%% @returns Address info for the specified host and service
%% @doc Retrieve address information for a given hostname and service.
%%
%% The `Host' parameter may be a fully qualified host name or a string
%% containing a valid dotted pair IP address. (Currently, only IPv4 is
%% supported).
%%
%% The `Service' parameter may be the name of a service (as defined via
%% `services(3)` or a string containing a decimal value of the same.
%%
%% Note that the `Host' or `String' parameter may be `undefined', but
%% not both.
%% @end
%%-----------------------------------------------------------------------------
-spec getaddrinfo(Host :: string() | undefined, Service :: service() | undefined) ->
{ok, AddrInfo :: addrinfo()} | {error, Reason :: term()}.
getaddrinfo(Host, Service) when
(is_list(Host) orelse Host =:= undefined) andalso
(is_list(Service) orelse Service =:= undefined) andalso
not (Host =:= undefined andalso Service =:= undefined)
->
?MODULE:getaddrinfo_nif(Host, Service).

%% @hidden
getaddrinfo_nif(_Host, _Service) ->
erlang:nif_error(undefined).
22 changes: 22 additions & 0 deletions src/libAtomVM/interop.c
Original file line number Diff line number Diff line change
Expand Up @@ -611,3 +611,25 @@ term interop_kv_get_value_default(term kv, AtomString key, term default_value, G
return default_value;
}
}

term interop_atom_term_select_atom(const AtomStringIntPair *table, int value, GlobalContext *global)
{
int i;
for (i = 0; table[i].as_val != NULL; i++) {
if (value == table[i].i_val) {
int global_atom_index = globalcontext_insert_atom(global, table[i].as_val);
return term_from_atom_index(global_atom_index);
}
}
return term_invalid_term();
}

term interop_chars_to_list(const char *chars, size_t len, Heap *heap)
{
term ret = term_nil();
for (int i = (int) len - 1; i >= 0; --i) {
term c_term = term_from_int(chars[i]);
ret = term_list_prepend(c_term, ret, heap);
}
return ret;
}
16 changes: 15 additions & 1 deletion src/libAtomVM/interop.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ term interop_proplist_get_value(term list, term key);
term interop_proplist_get_value_default(term list, term key, term default_value);
term interop_map_get_value(GlobalContext *glb, term map, term key);
term interop_map_get_value_default(GlobalContext *glb, term map, term key, term default_value);
term interop_chars_to_list(const char *chars, size_t len, Heap *heap);

NO_DISCARD InteropFunctionResult interop_iolist_size(term t, size_t *size);
NO_DISCARD InteropFunctionResult interop_write_iolist(term t, char *p);
Expand All @@ -95,14 +96,27 @@ NO_DISCARD enum UnicodeConversionResult interop_chardata_to_bytes(term t, uint8_
* @details Allows to quickly translate atoms to any integer constant. This function is useful for
* creating switch statements for atom values.
* A linear search is performed, so table entries should be sorted by frequency.
* @param table an array AtomStringIntPair structs, teminated with a default entry marked with
* @param table an array AtomStringIntPair structs, terminated with a default entry marked with
* SELECT_INT_DEFAULT macro.
* @param atom the atom used for comparison.
* @param global the global context.
* @returns the found int value which corresponds to the given atom.
*/
int interop_atom_term_select_int(const AtomStringIntPair *table, term atom, GlobalContext *global);

/**
* @brief Finds the first matching atom in an atoms table .
*
* @details Allows to quickly translate integer constants to an atom in an atoms table.
* This function is the inverse of interop_atom_term_select_int.
* @param table an array AtomStringIntPair structs, terminated with a default entry marked with
* SELECT_INT_DEFAULT macro.
* @param value the in value used for comparison.
* @param global the global context.
* @returns the found atom which corresponds to the given int value.
*/
term interop_atom_term_select_atom(const AtomStringIntPair *table, int value, GlobalContext *global);

/**
* @brief Get a value given a key (as AtomString) from any proplist or map
*
Expand Down
Loading

0 comments on commit a8e0f66

Please sign in to comment.