diff options
author | Raimo Niskanen <[email protected]> | 2018-10-12 12:27:17 +0200 |
---|---|---|
committer | GitHub <[email protected]> | 2018-10-12 12:27:17 +0200 |
commit | ac243aeb32d3379a51564d4f2312bbb1a00a5271 (patch) | |
tree | fb9a2c23eb3b81548443bcbf2009304d4ca833e1 | |
parent | 6b3d6f4a3d73cae7ff87b10b0aa5be6ff8c0a10a (diff) | |
parent | 2c0354281b7d47e60d6d44a130b601d5090de4d1 (diff) | |
download | otp-ac243aeb32d3379a51564d4f2312bbb1a00a5271.tar.gz otp-ac243aeb32d3379a51564d4f2312bbb1a00a5271.tar.bz2 otp-ac243aeb32d3379a51564d4f2312bbb1a00a5271.zip |
Merge pull request #1974 from RaimoNiskanen/raimo/getifaddrs-netns/ERIERL-189/OTP-15121
Implement {netns,NS} option for inet:getifaddrs/1 and friends
-rw-r--r-- | erts/emulator/drivers/common/inet_drv.c | 76 | ||||
-rw-r--r-- | erts/preloaded/ebin/prim_inet.beam | bin | 79028 -> 80120 bytes | |||
-rw-r--r-- | erts/preloaded/src/prim_inet.erl | 127 | ||||
-rw-r--r-- | lib/kernel/doc/src/inet.xml | 182 | ||||
-rw-r--r-- | lib/kernel/src/inet.erl | 82 | ||||
-rw-r--r-- | lib/kernel/test/inet_SUITE.erl | 172 |
6 files changed, 447 insertions, 192 deletions
diff --git a/erts/emulator/drivers/common/inet_drv.c b/erts/emulator/drivers/common/inet_drv.c index 7f20477363..6d1c5cf6cc 100644 --- a/erts/emulator/drivers/common/inet_drv.c +++ b/erts/emulator/drivers/common/inet_drv.c @@ -5178,6 +5178,71 @@ static int hwaddr_libdlpi_lookup(const char *ifnm, } #endif +#ifdef HAVE_GETIFADDRS +/* Returns 0 for success and errno() for failure */ +static int call_getifaddrs(inet_descriptor* desc_p, struct ifaddrs **ifa_pp) +{ + int result, save_errno; +#ifdef HAVE_SETNS + int current_ns; + + current_ns = 0; + if (desc_p->netns != NULL) { + int new_ns; + /* Temporarily change network namespace for this thread + * over the getifaddrs() call + */ + current_ns = open("/proc/self/ns/net", O_RDONLY); + if (current_ns == INVALID_SOCKET) + return sock_errno(); + new_ns = open(desc_p->netns, O_RDONLY); + if (new_ns == INVALID_SOCKET) { + save_errno = sock_errno(); + while (close(current_ns) == INVALID_SOCKET && + sock_errno() == EINTR); + return save_errno; + } + if (setns(new_ns, CLONE_NEWNET) != 0) { + save_errno = sock_errno(); + while (close(new_ns) == INVALID_SOCKET && + sock_errno() == EINTR); + while (close(current_ns) == INVALID_SOCKET && + sock_errno() == EINTR); + return save_errno; + } + else { + while (close(new_ns) == INVALID_SOCKET && + sock_errno() == EINTR); + } + } +#endif + save_errno = 0; + result = getifaddrs(ifa_pp); + if (result < 0) + save_errno = sock_errno(); +#ifdef HAVE_SETNS + if (desc_p->netns != NULL) { + /* Restore network namespace */ + if (setns(current_ns, CLONE_NEWNET) != 0) { + /* XXX Failed to restore network namespace. + * What to do? Tidy up and return an error... + * Note that the thread now might still be in the set namespace. + * Can this even happen? Should the emulator be aborted? + */ + if (result >= 0) { + /* We got a result but have to waste it */ + save_errno = sock_errno(); + freeifaddrs(*ifa_pp); + } + } + while (close(current_ns) == INVALID_SOCKET && + sock_errno() == EINTR); + } +#endif + return save_errno; +} +#endif /* #ifdef HAVE_GETIFADDRS */ + /* FIXME: temporary hack */ #ifndef IFHWADDRLEN #define IFHWADDRLEN 6 @@ -5255,8 +5320,8 @@ static ErlDrvSSizeT inet_ctl_ifget(inet_descriptor* desc, struct sockaddr_dl *sdlp; int found = 0; - if (getifaddrs(&ifa) == -1) - goto error; + if (call_getifaddrs(desc, &ifa) != 0) + goto error; for (ifp = ifa; ifp; ifp = ifp->ifa_next) { if ((ifp->ifa_addr->sa_family == AF_LINK) && @@ -5974,6 +6039,7 @@ static ErlDrvSSizeT inet_ctl_getifaddrs(inet_descriptor* desc_p, ErlDrvSizeT buf_size; char *buf_p; char *buf_alloc_p; + int save_errno; buf_size = GETIFADDRS_BUFSZ; buf_alloc_p = ALLOC(GETIFADDRS_BUFSZ); @@ -6008,9 +6074,9 @@ static ErlDrvSSizeT inet_ctl_getifaddrs(inet_descriptor* desc_p, } \ } while (0) - if (getifaddrs(&ifa_p) < 0) { - return ctl_error(sock_errno(), rbuf_pp, rsize); - } + if ((save_errno = call_getifaddrs(desc_p, &ifa_p)) != 0) + return ctl_error(save_errno, rbuf_pp, rsize); + ifa_free_p = ifa_p; *buf_p++ = INET_REP_OK; for (; ifa_p; ifa_p = ifa_p->ifa_next) { diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam Binary files differindex 4a345f8152..eebfe19a11 100644 --- a/erts/preloaded/ebin/prim_inet.beam +++ b/erts/preloaded/ebin/prim_inet.beam diff --git a/erts/preloaded/src/prim_inet.erl b/erts/preloaded/src/prim_inet.erl index 8169943dde..1da852deb2 100644 --- a/erts/preloaded/src/prim_inet.erl +++ b/erts/preloaded/src/prim_inet.erl @@ -870,9 +870,9 @@ chgopts(S, Opts) when is_port(S), is_list(Opts) -> getifaddrs(S) when is_port(S) -> case ctl_cmd(S, ?INET_REQ_GETIFADDRS, []) of - {ok, Data} -> - {ok, comp_ifaddrs(build_ifaddrs(Data), ktree_empty())}; - {error,enotsup} -> + {ok, Data} -> + {ok, comp_ifaddrs(build_ifaddrs(Data))}; + {error,enotsup} -> case getiflist(S) of {ok, IFs} -> {ok, getifaddrs_ifget(S, IFs)}; @@ -881,30 +881,75 @@ getifaddrs(S) when is_port(S) -> Err2 -> Err2 end. -%% Restructure interface properties per interface and remove duplicates - -comp_ifaddrs([{If,Opts}|IfOpts], T) -> - case ktree_is_defined(If, T) of - true -> - OptSet = comp_ifaddrs_add(ktree_get(If, T), Opts), - comp_ifaddrs(IfOpts, ktree_update(If, OptSet, T)); - false -> - OptSet = comp_ifaddrs_add(ktree_empty(), Opts), - comp_ifaddrs(IfOpts, ktree_insert(If, OptSet, T)) - end; -comp_ifaddrs([], T) -> - [{If,ktree_keys(ktree_get(If, T))} || If <- ktree_keys(T)]. - -comp_ifaddrs_add(OptSet, [Opt|Opts]) -> - case ktree_is_defined(Opt, OptSet) of - true - when element(1, Opt) =:= flags; - element(1, Opt) =:= hwaddr -> - comp_ifaddrs_add(OptSet, Opts); - _ -> - comp_ifaddrs_add(ktree_insert(Opt, undefined, OptSet), Opts) +%% Restructure interface properties per interface + +comp_ifaddrs(IfOpts) -> + comp_ifaddrs(IfOpts, ktree_empty()). +%% +comp_ifaddrs([{If,[{flags,Flags}|Opts]}|IfOpts], IfT) -> + case ktree_is_defined(If, IfT) of + true -> + comp_ifaddrs( + IfOpts, + ktree_update( + If, + comp_ifaddrs_flags(Flags, Opts, ktree_get(If, IfT)), + IfT)); + false -> + comp_ifaddrs( + IfOpts, + ktree_insert( + If, + comp_ifaddrs_flags(Flags, Opts, ktree_empty()), + IfT)) end; -comp_ifaddrs_add(OptSet, []) -> OptSet. +comp_ifaddrs([], IfT) -> + comp_ifaddrs_2(ktree_keys(IfT), IfT). + +comp_ifaddrs_flags(Flags, Opts, FlagsT) -> + case ktree_is_defined(Flags, FlagsT) of + true -> + ktree_update( + Flags, + rev(Opts, ktree_get(Flags, FlagsT)), + FlagsT); + false -> + ktree_insert(Flags, rev(Opts), FlagsT) + end. + +comp_ifaddrs_2([If|Ifs], IfT) -> + FlagsT = ktree_get(If, IfT), + [{If,comp_ifaddrs_3(ktree_keys(FlagsT), FlagsT)} + | comp_ifaddrs_2(Ifs, IfT)]; +comp_ifaddrs_2([], _IfT) -> + []. +%% +comp_ifaddrs_3([Flags|FlagsL], FlagsT) -> + [{flags,Flags}|hwaddr_last(rev(ktree_get(Flags, FlagsT)))] + ++ hwaddr_last(comp_ifaddrs_3(FlagsL, FlagsT)); +comp_ifaddrs_3([], _FlagsT) -> + []. + +%% Place hwaddr last to look more like legacy emulation +hwaddr_last(Opts) -> + hwaddr_last(Opts, Opts, []). +%% +hwaddr_last([{hwaddr,_} = Opt|Opts], L, R) -> + hwaddr_last(Opts, L, [Opt|R]); +hwaddr_last([_|Opts], L, R) -> + hwaddr_last(Opts, L, R); +hwaddr_last([], L, []) -> + L; +hwaddr_last([], L, R) -> + rev(hwaddr_last(L, []), rev(R)). +%% +hwaddr_last([{hwaddr,_}|Opts], R) -> + hwaddr_last(Opts, R); +hwaddr_last([Opt|Opts], R) -> + hwaddr_last(Opts, [Opt|R]); +hwaddr_last([], R) -> + R. + %% Legacy emulation of getifaddrs @@ -912,21 +957,19 @@ getifaddrs_ifget(_, []) -> []; getifaddrs_ifget(S, [IF|IFs]) -> case ifget(S, IF, [flags]) of {ok,[{flags,Flags}]=FlagsVals} -> - BroadOpts = - case member(broadcast, Flags) of - true -> - [broadaddr,hwaddr]; - false -> - [hwaddr] - end, - P2POpts = - case member(pointtopoint, Flags) of - true -> - [dstaddr|BroadOpts]; - false -> - BroadOpts - end, - getifaddrs_ifget(S, IFs, IF, FlagsVals, [addr,netmask|P2POpts]); + GetOpts = + case member(pointtopoint, Flags) of + true -> + [dstaddr,hwaddr]; + false -> + case member(broadcast, Flags) of + true -> + [broadaddr,hwaddr]; + false -> + [hwaddr] + end + end, + getifaddrs_ifget(S, IFs, IF, FlagsVals, [addr,netmask|GetOpts]); _ -> getifaddrs_ifget(S, IFs, IF, [], [addr,netmask,hwaddr]) end. @@ -2500,7 +2543,7 @@ get_addrs([F|Addrs]) -> [Addr|get_addrs(Rest)]. get_addr(?INET_AF_LOCAL, [N|Addr]) -> - {A,Rest} = lists:split(N, Addr), + {A,Rest} = split(N, Addr), {{local,iolist_to_binary(A)},Rest}; get_addr(?INET_AF_UNSPEC, Rest) -> {{unspec,<<>>},Rest}; diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml index ed775d67eb..127c110df4 100644 --- a/lib/kernel/doc/src/inet.xml +++ b/lib/kernel/doc/src/inet.xml @@ -198,6 +198,79 @@ fe80::204:acff:fe17:bf38 </desc> </datatype> <datatype> + <name name="getifaddrs_ifopts"/> + <desc> + <p> + Interface address description list returned from + <seealso marker="#getifaddrs/0"><c>getifaddrs/0,1</c></seealso> + for a named interface, translated from the returned + data of the POSIX API function <c>getaddrinfo()</c>. + </p> + <p> + <c><anno>Hwaddr</anno></c> is hardware dependent, + for example, on Ethernet interfaces it is + the 6-byte Ethernet address (MAC address (EUI-48 address)). + </p> + <p> + The tuples <c>{addr,<anno>Addr</anno>}</c>, + <c>{netmask,<anno>Netmask</anno>}</c>, and possibly + <c>{broadaddr,<anno>Broadaddr</anno>}</c> or + <c>{dstaddr,<anno>Dstaddr</anno>}</c> + are repeated in the list + if the interface has got multiple addresses. + An interface may have multiple <c>{flag,_}</c> tuples + for example if it has different flags + for different address families. + Multiple <c>{hwaddr,<anno>Hwaddr</anno>}</c> tuples + is hard to say anything definite about, though. + The tuple <c>{flag,<anno>Flags</anno>}</c> is mandatory, + all others are optional. + </p> + <p> + Do not rely too much on the order + of <c><anno>Flags</anno></c> atoms + or the <c><anno>Ifopt</anno></c> tuples. + There are however some rules: + </p> + <list type="bulleted"> + <item><p> + A <c>{flag,_}</c> tuple applies to all other tuples that follow. + </p></item> + <item><p> + Immediately after <c>{addr,_}</c> follows <c>{netmask,_}</c>. + </p></item> + <item><p> + Immediately thereafter may <c>{broadaddr,_}</c> follow + if <c>broadcast</c> is member of <c><anno>Flags</anno></c>, + or <c>{dstaddr,_}</c> if <c>pointtopoint</c> + is member of <c><anno>Flags</anno></c>. + Both <c>{dstaddr,_}</c> and <c>{broadaddr,_}</c> + does not occur for the same <c>{addr,_}</c>. + </p></item> + <item><p> + Any <c>{netmask,_}</c>, <c>{broadaddr,_}</c>, or + <c>{dstaddr,_}</c> tuples that follow an + <c>{addr,<anno>Addr</anno>}</c> + tuple concerns the address <c><anno>Addr</anno></c>. + </p></item> + </list> + <p> + The tuple <c>{hwaddr,_}</c> is not returned on Solaris, as the + hardware address historically belongs to the link layer + and it is not returned by the Solaris API function + <c>getaddrinfo()</c>. + </p> + <warning> + <p> + On Windows, the data is fetched from different + OS API functions, so the <c><anno>Netmask</anno></c> + and <c><anno>Broadaddr</anno></c> values may be calculated, + just as some <c><anno>Flags</anno></c> values. + </p> + </warning> + </desc> + </datatype> + <datatype> <name name="posix"/> <desc> <p>An atom that is named from the POSIX error codes used in Unix, @@ -324,38 +397,64 @@ fe80::204:acff:fe17:bf38 <name name="getifaddrs" arity="0"/> <fsummary>Return a list of interfaces and their addresses.</fsummary> <desc> - <p>Returns a list of 2-tuples containing interface names and the - interface addresses. <c><anno>Ifname</anno></c> is a Unicode string. - <c><anno>Hwaddr</anno></c> is hardware dependent, for example, on - Ethernet interfaces - it is the 6-byte Ethernet address (MAC address (EUI-48 address)).</p> - <p>The tuples <c>{addr,<anno>Addr</anno>}</c>, <c>{netmask,_}</c>, and - <c>{broadaddr,_}</c> are repeated in the result list if the interface - has multiple addresses. If you come across an interface with - multiple <c>{flag,_}</c> or <c>{hwaddr,_}</c> tuples, you have - a strange interface or possibly a bug in this function. The tuple - <c>{flag,_}</c> is mandatory, all others are optional.</p> - <p>Do not rely too much on the order of <c><anno>Flag</anno></c> atoms - or <c><anno>Ifopt</anno></c> tuples. There are however some rules:</p> - <list type="bulleted"> - <item><p>Immediately after - <c>{addr,_}</c> follows <c>{netmask,_}</c>.</p></item> - <item><p>Immediately thereafter follows <c>{broadaddr,_}</c> if flag - <c>broadcast</c> is <em>not</em> set and flag - <c>pointtopoint</c> <em>is</em> set.</p></item> - <item><p>Any <c>{netmask,_}</c>, <c>{broadaddr,_}</c>, or - <c>{dstaddr,_}</c> tuples that follow an <c>{addr,_}</c> - tuple concerns that address.</p></item> - </list> - <p>The tuple <c>{hwaddr,_}</c> is not returned on Solaris, as the - hardware address historically belongs to the link layer and only - the superuser can read such addresses.</p> - <warning> - <p>On Windows, the data is fetched from different OS API functions, - so the <c><anno>Netmask</anno></c> and <c><anno>Broadaddr</anno></c> - values can be calculated, just as some <c><anno>Flag</anno></c> - values. Report flagrant bugs.</p> - </warning> + <p> + Returns a list of 2-tuples containing interface names and + the interfaces' addresses. <c><anno>Ifname</anno></c> + is a Unicode string and + <c><anno>Ifopts</anno></c> is a list of + interface address description tuples. + </p> + <p> + The interface address description tuples + are documented under the type of the + <seealso marker="#type-getifaddrs_ifopts"> + <c><anno>Ifopts</anno></c> + </seealso> + value. + </p> + </desc> + </func> + + <func> + <name>getifaddrs(Opts) -> + {ok, [{Ifname, Ifopts}]} | {error, Posix} + </name> + <fsummary>Return a list of interfaces and their addresses.</fsummary> + <type> + <v> + Opts = [{netns, Namespace}] + </v> + <v> + Namespace = + <seealso marker="file#type-filename_all"> + file:filename_all() + </seealso> + </v> + <v>Ifname = string()</v> + <v> + Ifopts = + <seealso marker="#type-getifaddrs_ifopts"> + getifaddrs_ifopts() + </seealso> + </v> + <v>Posix = <seealso marker="#type-posix">posix()</seealso></v> + </type> + <desc> + <p> + The same as + <seealso marker="#getifaddrs/0"><c>getifaddrs/0</c></seealso> + but the <c>Option</c> + <c>{netns, Namespace}</c> sets a network namespace + for the OS call, on platforms that supports that feature. + </p> + <p> + See the socket option + <seealso marker="#option-netns"> + <c>{netns, Namespace}</c> + </seealso> + under + <seealso marker="#setopts/2"><c>setopts/2</c></seealso>. + </p> </desc> </func> @@ -950,20 +1049,29 @@ get_tcpi_sacked(Sock) -> </item> <tag><c>{mode, Mode :: binary | list}</c></tag> <item> - <p>Received <c>Packet</c> is delivered as defined by <c>Mode</c>. + <p> + Received <c>Packet</c> is delivered as defined by <c>Mode</c>. </p> </item> - <tag><c>{netns, Namespace :: file:filename_all()}</c></tag> + <tag> + <marker id="option-netns"/> + <c>{netns, Namespace :: file:filename_all()}</c> + </tag> <item> - <p>Sets a network namespace for the socket. Parameter + <p> + Sets a network namespace for the socket. Parameter <c>Namespace</c> is a filename defining the namespace, for example, <c>"/var/run/netns/example"</c>, typically created by command <c>ip netns add example</c>. This option must be used in a function call that creates a socket, that is, <seealso marker="gen_tcp#connect/3"><c>gen_tcp:connect/3,4</c></seealso>, <seealso marker="gen_tcp#listen/2"><c>gen_tcp:listen/2</c></seealso>, - <seealso marker="gen_udp#open/1"><c>gen_udp:open/1,2</c></seealso>, or - <seealso marker="gen_sctp#open/0"><c>gen_sctp:open/0,1,2</c></seealso>.</p> + <seealso marker="gen_udp#open/1"><c>gen_udp:open/1,2</c></seealso> + or + <seealso marker="gen_sctp#open/0"><c>gen_sctp:open/0,1,2</c></seealso>, + and also + <seealso marker="#getifaddrs/1"><c>getifaddrs/1</c></seealso>. + </p> <p>This option uses the Linux-specific syscall <c>setns()</c>, such as in Linux kernel 3.0 or later, and therefore only exists when the runtime system diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index 5dd68dc285..9f22eb6aaa 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -154,6 +154,15 @@ 'running' | 'multicast' | 'loopback']} | {'hwaddr', ether_address()}. +-type getifaddrs_ifopts() :: + [Ifopt :: {flags, Flags :: [up | broadcast | loopback | + pointtopoint | running | multicast]} | + {addr, Addr :: ip_address()} | + {netmask, Netmask :: ip_address()} | + {broadaddr, Broadaddr :: ip_address()} | + {dstaddr, Dstaddr :: ip_address()} | + {hwaddr, Hwaddr :: [byte()]}]. + -type address_family() :: 'inet' | 'inet6' | 'local'. -type socket_protocol() :: 'tcp' | 'udp' | 'sctp'. -type socket_type() :: 'stream' | 'dgram' | 'seqpacket'. @@ -321,32 +330,32 @@ getopts(Socket, Opts) -> Other end. --spec getifaddrs(Socket :: socket()) -> - {'ok', [string()]} | {'error', posix()}. - +-spec getifaddrs( + [Option :: {netns, Namespace :: file:filename_all()}] + | socket()) -> + {'ok', [{Ifname :: string(), + Ifopts :: getifaddrs_ifopts()}]} + | {'error', posix()}. +getifaddrs(Opts) when is_list(Opts) -> + withsocket(fun(S) -> prim_inet:getifaddrs(S) end, Opts); getifaddrs(Socket) -> prim_inet:getifaddrs(Socket). --spec getifaddrs() -> {ok, Iflist} | {error, posix()} when - Iflist :: [{Ifname,[Ifopt]}], - Ifname :: string(), - Ifopt :: {flags,[Flag]} | {addr,Addr} | {netmask,Netmask} - | {broadaddr,Broadaddr} | {dstaddr,Dstaddr} - | {hwaddr,Hwaddr}, - Flag :: up | broadcast | loopback | pointtopoint - | running | multicast, - Addr :: ip_address(), - Netmask :: ip_address(), - Broadaddr :: ip_address(), - Dstaddr :: ip_address(), - Hwaddr :: [byte()]. - +-spec getifaddrs() -> + {'ok', [{Ifname :: string(), + Ifopts :: getifaddrs_ifopts()}]} + | {'error', posix()}. getifaddrs() -> withsocket(fun(S) -> prim_inet:getifaddrs(S) end). --spec getiflist(Socket :: socket()) -> - {'ok', [string()]} | {'error', posix()}. +-spec getiflist( + [Option :: {netns, Namespace :: file:filename_all()}] + | socket()) -> + {'ok', [string()]} | {'error', posix()}. + +getiflist(Opts) when is_list(Opts) -> + withsocket(fun(S) -> prim_inet:getiflist(S) end, Opts); getiflist(Socket) -> prim_inet:getiflist(Socket). @@ -363,11 +372,19 @@ getiflist() -> ifget(Socket, Name, Opts) -> prim_inet:ifget(Socket, Name, Opts). --spec ifget(Name :: string() | atom(), Opts :: [if_getopt()]) -> +-spec ifget( + Name :: string() | atom(), + Opts :: [if_getopt() | + {netns, Namespace :: file:filename_all()}]) -> {'ok', [if_getopt_result()]} | {'error', posix()}. ifget(Name, Opts) -> - withsocket(fun(S) -> prim_inet:ifget(S, Name, Opts) end). + {NSOpts,IFOpts} = + lists:partition( + fun ({netns,_}) -> true; + (_) -> false + end, Opts), + withsocket(fun(S) -> prim_inet:ifget(S, Name, IFOpts) end, NSOpts). -spec ifset(Socket :: socket(), Name :: string() | atom(), @@ -377,11 +394,19 @@ ifget(Name, Opts) -> ifset(Socket, Name, Opts) -> prim_inet:ifset(Socket, Name, Opts). --spec ifset(Name :: string() | atom(), Opts :: [if_setopt()]) -> +-spec ifset( + Name :: string() | atom(), + Opts :: [if_setopt() | + {netns, Namespace :: file:filename_all()}]) -> 'ok' | {'error', posix()}. ifset(Name, Opts) -> - withsocket(fun(S) -> prim_inet:ifset(S, Name, Opts) end). + {NSOpts,IFOpts} = + lists:partition( + fun ({netns,_}) -> true; + (_) -> false + end, Opts), + withsocket(fun(S) -> prim_inet:ifset(S, Name, IFOpts) end, NSOpts). -spec getif() -> {'ok', [{ip_address(), ip_address() | 'undefined', ip_address()}]} | @@ -391,10 +416,14 @@ getif() -> withsocket(fun(S) -> getif(S) end). %% backwards compatible getif --spec getif(Socket :: socket()) -> +-spec getif( + [Option :: {netns, Namespace :: file:filename_all()}] + | socket()) -> {'ok', [{ip_address(), ip_address() | 'undefined', ip_address()}]} | {'error', posix()}. +getif(Opts) when is_list(Opts) -> + withsocket(fun(S) -> getif(S) end, Opts); getif(Socket) -> case prim_inet:getiflist(Socket) of {ok, IfList} -> @@ -415,7 +444,10 @@ getif(Socket) -> end. withsocket(Fun) -> - case inet_udp:open(0,[]) of + withsocket(Fun, []). +%% +withsocket(Fun, Opts) -> + case inet_udp:open(0, Opts) of {ok,Socket} -> Res = Fun(Socket), inet_udp:close(Socket), diff --git a/lib/kernel/test/inet_SUITE.erl b/lib/kernel/test/inet_SUITE.erl index 2e5f8c7d2c..f436eafad3 100644 --- a/lib/kernel/test/inet_SUITE.erl +++ b/lib/kernel/test/inet_SUITE.erl @@ -1060,28 +1060,26 @@ getservbyname_overflow(Config) when is_list(Config) -> getifaddrs(Config) when is_list (Config) -> {ok,IfAddrs} = inet:getifaddrs(), io:format("IfAddrs = ~p.~n", [IfAddrs]), - case - {os:type(), - [If || - {If,Opts} <- IfAddrs, - lists:keymember(hwaddr, 1, Opts)]} of - {{unix,sunos},[]} -> ok; - {OT,[]} -> - ct:fail({should_have_hwaddr,OT}); - _ -> ok + case [If || {If,Opts} <- IfAddrs, lists:keymember(hwaddr, 1, Opts)] of + [] -> + case os:type() of + {unix,sunos} -> ok; + OT -> + ct:fail({should_have_hwaddr,OT}) + end; + [_|_] -> ok end, - Addrs = - [element(1, A) || A <- ifaddrs(IfAddrs)], + Addrs = ifaddrs(IfAddrs), io:format("Addrs = ~p.~n", [Addrs]), [check_addr(Addr) || Addr <- Addrs], ok. -check_addr({addr,Addr}) +check_addr(Addr) when tuple_size(Addr) =:= 8, element(1, Addr) band 16#FFC0 =:= 16#FE80 -> io:format("Addr: ~p link local; SKIPPED!~n", [Addr]), ok; -check_addr({addr,Addr}) -> +check_addr(Addr) -> io:format("Addr: ~p.~n", [Addr]), Ping = "ping", Pong = "pong", @@ -1097,78 +1095,86 @@ check_addr({addr,Addr}) -> ok = gen_tcp:close(S2), ok = gen_tcp:close(L). --record(ifopts, {name,flags,addrs=[],hwaddr}). - -ifaddrs([]) -> []; -ifaddrs([{If,Opts}|IOs]) -> - #ifopts{flags=F} = Ifopts = check_ifopts(Opts, #ifopts{name=If}), - case F of - {flags,Flags} -> - case lists:member(running, Flags) of - true -> Ifopts#ifopts.addrs; - false -> [] - end ++ ifaddrs(IOs); - undefined -> - ifaddrs(IOs) +ifaddrs(IfOpts) -> + IfMap = collect_ifopts(IfOpts), + ChkFun = + fun Self({{_,Flags} = Key, Opts}, ok) -> + Broadcast = lists:member(broadcast, Flags), + P2P = lists:member(pointtopoint, Flags), + case Opts of + [{addr,_},{netmask,_},{broadaddr,_}|Os] + when Broadcast -> + Self({Key, Os}, ok); + [{addr,_},{netmask,_},{dstaddr,_}|Os] + when P2P -> + Self({Key, Os}, ok); + [{addr,_},{netmask,_}|Os] -> + Self({Key, Os}, ok); + [{hwaddr,_}|Os] -> + Self({Key, Os}, ok); + [] -> + ok + end + end, + fold_ifopts(ChkFun, ok, IfMap), + AddrsFun = + fun ({{_,Flags}, Opts}, Acc) -> + case + lists:member(running, Flags) + andalso (not lists:member(pointtopoint, Flags)) + of + true -> + lists:reverse( + [Addr || {addr,Addr} <- Opts], + Acc); + false -> + Acc + end + end, + fold_ifopts(AddrsFun, [], IfMap). + +collect_ifopts(IfOpts) -> + collect_ifopts(IfOpts, #{}). +%% +collect_ifopts(IfOpts, IfMap) -> + case IfOpts of + [{If,[{flags,Flags}|Opts]}|IfOs] -> + Key = {If,Flags}, + case maps:is_key(Key, IfMap) of + true -> + ct:fail({unexpected_ifopts,IfOpts,IfMap}); + false -> + collect_ifopts(IfOs, IfMap, Opts, Key, []) + end; + [] -> + IfMap; + _ -> + ct:fail({unexpected_ifopts,IfOpts,IfMap}) + end. +%% +collect_ifopts(IfOpts, IfMap, Opts, Key, R) -> + case Opts of + [{flags,_}|_] -> + {If,_} = Key, + collect_ifopts( + [{If,Opts}|IfOpts], maps:put(Key, lists:reverse(R), IfMap)); + [OptVal|Os] -> + collect_ifopts(IfOpts, IfMap, Os, Key, [OptVal|R]); + [] -> + collect_ifopts(IfOpts, maps:put(Key, lists:reverse(R), IfMap)) end. -check_ifopts([], #ifopts{flags=F,addrs=Raddrs}=Ifopts) -> - Addrs = lists:reverse(Raddrs), - R = Ifopts#ifopts{addrs=Addrs}, - io:format("~p.~n", [R]), - %% See how we did... - {flags,Flags} = F, - case lists:member(broadcast, Flags) of - true -> - [case A of - {{addr,_},{netmask,_},{broadaddr,_}} -> - A; - {{addr,T},{netmask,_}} when tuple_size(T) =:= 8 -> - A - end || A <- Addrs]; - false -> - case lists:member(pointtopoint, Flags) of - true -> - [case A of - {{addr,_},{netmask,_},{dstaddr,_}} -> - A - end || A <- Addrs]; - false -> - [case A of - {{addr,_},{netmask,_}} -> - A - end || A <- Addrs] - end - end, - R; -check_ifopts([{flags,_}=F|Opts], #ifopts{flags=undefined}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{flags=F}); -check_ifopts([{flags,_}=F|Opts], #ifopts{flags=Flags}=Ifopts) -> - case F of - Flags -> - check_ifopts(Opts, Ifopts); - _ -> - ct:fail({multiple_flags,F,Ifopts}) - end; -check_ifopts( - [{addr,_}=A,{netmask,_}=N,{dstaddr,_}=D|Opts], - #ifopts{addrs=Addrs}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{addrs=[{A,N,D}|Addrs]}); -check_ifopts( - [{addr,_}=A,{netmask,_}=N,{broadaddr,_}=B|Opts], - #ifopts{addrs=Addrs}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{addrs=[{A,N,B}|Addrs]}); -check_ifopts( - [{addr,_}=A,{netmask,_}=N|Opts], - #ifopts{addrs=Addrs}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{addrs=[{A,N}|Addrs]}); -check_ifopts([{addr,_}=A|Opts], #ifopts{addrs=Addrs}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{addrs=[{A}|Addrs]}); -check_ifopts([{hwaddr,Hwaddr}=H|Opts], #ifopts{hwaddr=undefined}=Ifopts) - when is_list(Hwaddr) -> - check_ifopts(Opts, Ifopts#ifopts{hwaddr=H}); -check_ifopts([{hwaddr,_}=H|_], #ifopts{}=Ifopts) -> - ct:fail({multiple_hwaddrs,H,Ifopts}). +fold_ifopts(Fun, Acc, IfMap) -> + fold_ifopts(Fun, Acc, IfMap, maps:keys(IfMap)). +%% +fold_ifopts(Fun, Acc, IfMap, Keys) -> + case Keys of + [Key|Ks] -> + Opts = maps:get(Key, IfMap), + fold_ifopts(Fun, Fun({Key,Opts}, Acc), IfMap, Ks); + [] -> + Acc + end. %% Works just like lists:member/2, except that any {127,_,_,_} tuple %% matches any other {127,_,_,_}. We do this to handle Linux systems |