diff options
author | Micael Karlberg <bmk@erlang.org> | 2019-11-18 18:38:32 +0100 |
---|---|---|
committer | Micael Karlberg <bmk@erlang.org> | 2019-11-25 18:28:27 +0100 |
commit | 0e70e47ddc56c3dfe7a277b27008fde6ab3a3dd0 (patch) | |
tree | a745c40e0b1fdac88aab1196ea8d497076c63933 | |
parent | d39a010596fec70b29611f32a13a274be75a0e63 (diff) | |
download | erlang-0e70e47ddc56c3dfe7a277b27008fde6ab3a3dd0.tar.gz |
[enet] Add getifaddrs
It works, but some of the packet address fields are still
not translated (just integers for now).
OTP-16212
-rw-r--r-- | erts/emulator/nifs/common/prim_net_nif.c | 652 | ||||
-rw-r--r-- | erts/emulator/nifs/common/socket_int.h | 19 | ||||
-rw-r--r-- | erts/emulator/nifs/common/socket_nif.c | 8 | ||||
-rw-r--r-- | erts/emulator/nifs/common/socket_util.c | 242 | ||||
-rw-r--r-- | erts/emulator/nifs/common/socket_util.h | 26 | ||||
-rw-r--r-- | erts/preloaded/ebin/prim_net.beam | bin | 4616 -> 1860 bytes | |||
-rw-r--r-- | erts/preloaded/src/prim_net.erl | 43 |
7 files changed, 979 insertions, 11 deletions
diff --git a/erts/emulator/nifs/common/prim_net_nif.c b/erts/emulator/nifs/common/prim_net_nif.c index ae219140bd..491b633716 100644 --- a/erts/emulator/nifs/common/prim_net_nif.c +++ b/erts/emulator/nifs/common/prim_net_nif.c @@ -164,6 +164,7 @@ #include "socket_dbg.h" #include "socket_int.h" +#include "socket_tarray.h" #include "socket_util.h" @@ -261,6 +262,7 @@ extern char* erl_errno_id(int error); ENET_NIF_FUNC_DEF(gethostname); \ ENET_NIF_FUNC_DEF(getnameinfo); \ ENET_NIF_FUNC_DEF(getaddrinfo); \ + ENET_NIF_FUNC_DEF(getifaddrs); \ ENET_NIF_FUNC_DEF(if_name2index); \ ENET_NIF_FUNC_DEF(if_index2name); \ ENET_NIF_FUNC_DEF(if_names); @@ -284,10 +286,12 @@ static ERL_NIF_TERM enet_getnameinfo(ErlNifEnv* env, static ERL_NIF_TERM enet_getaddrinfo(ErlNifEnv* env, char* host, char* serv); +static ERL_NIF_TERM enet_getifaddrs(ErlNifEnv* env, + char* netns); static ERL_NIF_TERM enet_if_name2index(ErlNifEnv* env, - char* ifn); + char* ifn); static ERL_NIF_TERM enet_if_index2name(ErlNifEnv* env, - unsigned int id); + unsigned int id); static ERL_NIF_TERM enet_if_names(ErlNifEnv* env); static unsigned int enet_if_names_length(struct if_nameindex* p); @@ -303,6 +307,36 @@ static void net_down(ErlNifEnv* env, const ErlNifMonitor* mon); */ +#ifdef HAVE_SETNS +static BOOLEAN_T enet_getifaddrs_netns(ErlNifEnv* env, + ERL_NIF_TERM map, + char** netns); +static ERL_NIF_TERM enet_getifaddrs(ErlNifEnv* env, + char* netns); +static ERL_NIF_TERM enet_getifaddrs_process(ErlNifEnv* env, + struct ifaddrs* ifap); +static unsigned int enet_getifaddrs_length(struct ifaddrs* ifap); +static BOOLEAN_T encode_ifaddrs(ErlNifEnv* env, + struct ifaddrs* ifap, + ERL_NIF_TERM* eifa); +static ERL_NIF_TERM encode_ifaddrs_name(ErlNifEnv* env, + char* name); +static ERL_NIF_TERM encode_ifaddrs_flags(ErlNifEnv* env, + unsigned int flags); +static ERL_NIF_TERM encode_ifaddrs_addr(ErlNifEnv* env, + struct sockaddr* sa); +static char* make_ifaddrs(ErlNifEnv* env, + ERL_NIF_TERM name, + ERL_NIF_TERM flags, + ERL_NIF_TERM addr, + ERL_NIF_TERM netmask, + ERL_NIF_TERM ifu_key, + ERL_NIF_TERM ifu_value, + ERL_NIF_TERM data, + ERL_NIF_TERM* ifAddrs); +static BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err); +static BOOLEAN_T restore_network_namespace(int ns, int* err); +#endif static BOOLEAN_T decode_nameinfo_flags(ErlNifEnv* env, const ERL_NIF_TERM eflags, int* flags); @@ -374,15 +408,32 @@ static const struct in6_addr in6addr_loopback = #define LOCAL_ATOMS \ LOCAL_ATOM_DECL(address_info); \ + LOCAL_ATOM_DECL(automedia); \ + LOCAL_ATOM_DECL(broadaddr); \ + LOCAL_ATOM_DECL(broadcast); \ LOCAL_ATOM_DECL(debug); \ + LOCAL_ATOM_DECL(dstaddr); \ + LOCAL_ATOM_DECL(dynamic); \ LOCAL_ATOM_DECL(host); \ LOCAL_ATOM_DECL(idn); \ + LOCAL_ATOM_DECL(master); \ + LOCAL_ATOM_DECL(multicast); \ + LOCAL_ATOM_DECL(name); \ LOCAL_ATOM_DECL(namereqd); \ LOCAL_ATOM_DECL(name_info); \ + LOCAL_ATOM_DECL(netmask); \ + LOCAL_ATOM_DECL(noarp); \ LOCAL_ATOM_DECL(nofqdn); \ + LOCAL_ATOM_DECL(notrailers); \ LOCAL_ATOM_DECL(numerichost); \ LOCAL_ATOM_DECL(numericserv); \ - LOCAL_ATOM_DECL(service); + LOCAL_ATOM_DECL(pointopoint); \ + LOCAL_ATOM_DECL(portsel); \ + LOCAL_ATOM_DECL(promisc); \ + LOCAL_ATOM_DECL(running); \ + LOCAL_ATOM_DECL(service); \ + LOCAL_ATOM_DECL(slave); \ + LOCAL_ATOM_DECL(up); #define LOCAL_ERROR_REASON_ATOMS \ LOCAL_ATOM_DECL(eaddrfamily); \ @@ -430,6 +481,7 @@ static ErlNifResourceTypeInit netInit = { * nif_gethostname/0 * nif_getnameinfo/2 * nif_getaddrinfo/3 + * nif_getifaddrs/1 * nif_if_name2index/1 * nif_if_index2name/1 * nif_if_names/0 @@ -824,8 +876,8 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, return enif_make_badarg(env); /* - if (decode_addrinfo_hints(env, eHints, &hints)) - return enif_make_badarg(env); + if (decode_addrinfo_hints(env, eHints, &hints)) + return enif_make_badarg(env); */ if ((hostName == NULL) && (servName == NULL)) @@ -840,8 +892,8 @@ ERL_NIF_TERM nif_getaddrinfo(ErlNifEnv* env, FREE(servName); /* - if (hints != NULL) - FREE(hints); + if (hints != NULL) + FREE(hints); */ NDBG( ("NET", @@ -961,6 +1013,488 @@ ERL_NIF_TERM enet_getaddrinfo(ErlNifEnv* env, /* ---------------------------------------------------------------------- + * nif_getifaddrs + * + * Description: + * Get interface addresses + * + * Arguments: + * Extra - A way to pass 'extra' arguments. + * Currently only used for netns (name space). + */ + +static +ERL_NIF_TERM nif_getifaddrs(ErlNifEnv* env, + int argc, + const ERL_NIF_TERM argv[]) +{ +#if defined(__WIN32__) + return enif_raise_exception(env, MKA(env, "notsup")); +#elif defined(HAVE_GETIFADDRS) + ERL_NIF_TERM extra; + char* netns; + ERL_NIF_TERM result; + + NDBG( ("NET", "nif_getifaddrs -> entry (%d)\r\n", argc) ); + + if ((argc != 1) || + !IS_MAP(env, argv[0])) { + return enif_make_badarg(env); + } + extra = argv[0]; + +#ifdef HAVE_SETNS + /* We *currently* only support one extra option: netns */ + if (!enet_getifaddrs_netns(env, extra, &netns)) { + NDBG( ("NET", "nif_getifaddrs -> namespace: %s\r\n", netns) ); + return enif_make_badarg(env); + } +#else + netns = NULL; +#endif + + result = enet_getifaddrs(env, netns); + + NDBG( ("NET", + "nif_getifaddrs -> done when result: " + "\r\n %T\r\n", result) ); + + return result; +#else // HAVE_GETIFADDRS + return esock_make_error(env, esock_atom_notsup); +#endif +} + + +#ifdef HAVE_GETIFADDRS +#ifdef HAVE_SETNS +/* enet_getifaddrs_netns - extract the netns field from the 'extra' map + * + * Note that the 'extra' map *may* contain other options, but here we + * only care about 'netns'. + */ +static +BOOLEAN_T enet_getifaddrs_netns(ErlNifEnv* env, ERL_NIF_TERM map, char** netns) +{ + size_t sz; + ERL_NIF_TERM key; + ERL_NIF_TERM value; + unsigned int len; + char* buf; + int written; + + /* Note that its acceptable that the extra map is empty */ + if (!enif_get_map_size(env, map, &sz) || + (sz != 1)) { + *netns = NULL; + return TRUE; + } + + /* Regardless of the content of the 'extra' map, we only care about 'netns' */ + key = enif_make_atom(env, "netns"); + if (!GET_MAP_VAL(env, map, key, &value)) { + *netns = NULL; + return TRUE; + } + + /* So far so good. The value should be a string, check. */ + if (!enif_is_list(env, value)) { + *netns = NULL; // Just in case... + return FALSE; + } + + if (!enif_get_list_length(env, value, &len)) { + *netns = NULL; // Just in case... + return FALSE; + } + + if ((buf = MALLOC(len+1)) == NULL) { + *netns = NULL; // Just in case... + return FALSE; + } + + written = enif_get_string(env, value, buf, len+1, ERL_NIF_LATIN1); + if (written == (len+1)) { + *netns = buf; + return TRUE; + } else { + *netns = NULL; // Just in case... + return FALSE; + } +} +#endif + + + +static +ERL_NIF_TERM enet_getifaddrs(ErlNifEnv* env, char* netns) +{ + ERL_NIF_TERM result; + struct ifaddrs* ifap; +#ifdef HAVE_SETNS + int current_ns = 0; + int save_errno; +#endif + + NDBG( ("NET", "enet_getifaddrs -> entry with" + "\r\n netns: %s" + "\r\n", ((netns == NULL) ? "NULL" : netns)) ); + +#ifdef HAVE_SETNS + if ((netns != NULL) && + !change_network_namespace(netns, ¤t_ns, &save_errno)) + return esock_make_error_errno(env, save_errno); +#endif + + if (0 == getifaddrs(&ifap)) { + result = enet_getifaddrs_process(env, ifap); + freeifaddrs(ifap); + } else { + save_errno = get_errno(); + + NDBG( ("NET", "enet_getifaddrs -> failed get addrs: %d", save_errno) ); + + result = esock_make_error_errno(env, save_errno); + } + + +#ifdef HAVE_SETNS + if ((netns != NULL) && + !restore_network_namespace(current_ns, &save_errno)) + return esock_make_error_errno(env, save_errno); + + if (netns != NULL) + FREE(netns); +#endif + + NDBG( ("NET", "enet_getifaddrs -> done when" + "\r\n result: %T" + "\r\n", result) ); + + return result; +} + + +static +ERL_NIF_TERM enet_getifaddrs_process(ErlNifEnv* env, struct ifaddrs* ifap) +{ + ERL_NIF_TERM result; + unsigned int len = ((ifap == NULL) ? 0 : enet_getifaddrs_length(ifap)); + + NDBG( ("NET", "enet_getifaddrs_process -> len: %d\r\n", len) ); + + if (len > 0) { + ERL_NIF_TERM* array = MALLOC(len * sizeof(ERL_NIF_TERM)); + unsigned int i = 0; + struct ifaddrs* p = ifap; + + while (i < len) { + ERL_NIF_TERM entry; + + if (!encode_ifaddrs(env, p, &entry)) { + /* If this fail, we are f*ed, so give up */ + FREE(array); + return esock_make_error(env, esock_atom_einval); + } + + NDBG( ("NET", "enet_getifaddrs_process -> entry: %T\r\n", entry) ); + + array[i] = entry; + p = p->ifa_next; + i++; + } + + NDBG( ("NET", "enet_getifaddrs_process -> all entries processed\r\n") ); + + result = esock_make_ok2(env, MKLA(env, array, len)); + FREE(array); + + } else { + result = esock_make_ok2(env, MKEL(env)); + } + + NDBG( ("NET", "enet_getifaddrs_process -> result: " + "\r\n %T\r\n", result) ); + + return result; +} + + + +/* Calculate the length of the interface adress linked list + * The list is NULL-terminated, so the only way is to + * iterate through the list until we find next = NULL. + */ +static +unsigned int enet_getifaddrs_length(struct ifaddrs* ifap) +{ + unsigned int len = 1; + struct ifaddrs* tmp; + BOOLEAN_T done = FALSE; + + tmp = ifap; + + while (!done) { + if (tmp->ifa_next != NULL) { + len++; + tmp = tmp->ifa_next; + } else { + done = TRUE; + } + } + + return len; +} + + + +static +BOOLEAN_T encode_ifaddrs(ErlNifEnv* env, + struct ifaddrs* ifap, + ERL_NIF_TERM* eifa) +{ + char* xres; + ERL_NIF_TERM ename, eflags, eaddr, enetmask, eifu_key, eifu_value, edata; + ERL_NIF_TERM eifAddrs; + + ename = encode_ifaddrs_name(env, ifap->ifa_name); + NDBG( ("NET", "encode_ifaddrs -> name: %T\r\n", ename) ); + eflags = encode_ifaddrs_flags(env, ifap->ifa_flags); + NDBG( ("NET", "encode_ifaddrs -> flags: %T\r\n", eflags) ); + eaddr = encode_ifaddrs_addr(env, ifap->ifa_addr); + NDBG( ("NET", "encode_ifaddrs -> addr: %T\r\n", eaddr) ); + enetmask = encode_ifaddrs_addr(env, ifap->ifa_netmask); + NDBG( ("NET", "encode_ifaddrs -> netmask: %T\r\n", enetmask) ); + if (ifap->ifa_dstaddr && (ifap->ifa_flags & IFF_POINTOPOINT)) { + eifu_key = atom_dstaddr; + eifu_value = encode_ifaddrs_addr(env, ifap->ifa_dstaddr); + } else if (ifap->ifa_broadaddr && (ifap->ifa_flags & IFF_BROADCAST)) { + eifu_key = atom_broadaddr; + eifu_value = encode_ifaddrs_addr(env, ifap->ifa_broadaddr); + } else { + eifu_key = esock_atom_undefined; + eifu_value = esock_atom_undefined; + } + NDBG( ("NET", "encode_ifaddrs -> ifu: " + "\r\n key: %T" + "\r\n val: %T" + "\r\n", eifu_key, eifu_value) ); + /* Don't know how to encode this yet... + * We don't even know the size... + */ + edata = esock_atom_undefined; + + if ((xres = make_ifaddrs(env, + ename, eflags, eaddr, enetmask, + eifu_key, eifu_value, edata, + &eifAddrs)) == NULL) { + NDBG( ("NET", "encode_ifaddrs -> encoded ifAddrs: %T\r\n", eifAddrs) ); + *eifa = eifAddrs; + + return TRUE; + + } else { + + /* *** Ouch *** + * + * Failed to construct the ifaddrs map => try for a minumum map + * + */ + ERL_NIF_TERM keys[] = {atom_name}; + ERL_NIF_TERM vals[] = {ename}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + + esock_warning_msg("Failed constructing IfAddr map for %s: %s", + ifap->ifa_name, xres); + + if (!MKMA(env, keys, vals, numKeys, eifa)) { + // We are in a bad way, give up */ + *eifa = esock_atom_undefined; + return FALSE; + } else { + return TRUE; + } + + } +} + + + +static +ERL_NIF_TERM encode_ifaddrs_name(ErlNifEnv* env, char* name) +{ + return ((name == NULL) ? esock_atom_undefined : MKS(env, name)); +} + + + +static +ERL_NIF_TERM encode_ifaddrs_flags(ErlNifEnv* env, unsigned int flags) +{ + SocketTArray ta = TARRAY_CREATE(16); + ERL_NIF_TERM eflags; + + if (flags & IFF_UP) + TARRAY_ADD(ta, atom_up); + + if (flags & IFF_BROADCAST) + TARRAY_ADD(ta, atom_broadcast); + + if (flags & IFF_DEBUG) + TARRAY_ADD(ta, atom_debug); + + if (flags & IFF_LOOPBACK) + TARRAY_ADD(ta, esock_atom_loopback); + + if (flags & IFF_POINTOPOINT) + TARRAY_ADD(ta, atom_pointopoint); + + if (flags & IFF_NOTRAILERS) + TARRAY_ADD(ta, atom_notrailers); + + if (flags & IFF_RUNNING) + TARRAY_ADD(ta, atom_running); + + if (flags & IFF_NOARP) + TARRAY_ADD(ta, atom_noarp); + + if (flags & IFF_PROMISC) + TARRAY_ADD(ta, atom_promisc); + + if (flags & IFF_MASTER) + TARRAY_ADD(ta, atom_master); + + if (flags & IFF_SLAVE) + TARRAY_ADD(ta, atom_slave); + + if (flags & IFF_MULTICAST) + TARRAY_ADD(ta, atom_multicast); + + if (flags & IFF_PORTSEL) + TARRAY_ADD(ta, atom_portsel); + + if (flags & IFF_AUTOMEDIA) + TARRAY_ADD(ta, atom_automedia); + + if (flags & IFF_DYNAMIC) + TARRAY_ADD(ta, atom_dynamic); + + TARRAY_TOLIST(ta, env, &eflags); + + return eflags; +} + + +static +ERL_NIF_TERM encode_ifaddrs_addr(ErlNifEnv* env, struct sockaddr* sa) +{ + ERL_NIF_TERM esa; + + if (sa != NULL) { + unsigned int sz = sizeof(ESockAddress); + if (esock_encode_sockaddr(env, (ESockAddress*) sa, sz, &esa) != NULL) { + NDBG( ("NET", "encode_ifaddrs_addr -> " + "encoding address field 0x%lX failed => use default\r\n", sa) ); + esa = esock_atom_undefined; + } + } else { + esa = esock_atom_undefined; + } + + return esa; +} + + +static +char* make_ifaddrs(ErlNifEnv* env, + ERL_NIF_TERM ename, + ERL_NIF_TERM eflags, + ERL_NIF_TERM eaddr, + ERL_NIF_TERM enetmask, + ERL_NIF_TERM eifu_key, + ERL_NIF_TERM eifu_value, + ERL_NIF_TERM edata, + ERL_NIF_TERM* eifAddrs) +{ + /* Several of these values can be (the atom) undefined, which + * means that they should *not* be included in the result map. + */ + ERL_NIF_TERM keys[6]; // There are only (at most) siz (6) fields... + ERL_NIF_TERM vals[6]; + unsigned int len = sizeof(keys) / sizeof(ERL_NIF_TERM); // Just in case... + unsigned int idx = 0; + + /* *** Name *** */ + NDBG( ("NET", "make_ifaddrs -> name: %T\r\n", ename) ); + keys[idx] = atom_name; + vals[idx] = ename; + idx++; + + /* *** Flags *** */ + NDBG( ("NET", "make_ifaddrs -> flags: %T\r\n", eflags) ); + keys[idx] = esock_atom_flags; + vals[idx] = eflags; + idx++; + + /* *** Addr (can be 'undefined' = NULL) *** */ + NDBG( ("NET", "make_ifaddrs -> addr: %T\r\n", eaddr) ); + if (COMPARE(eaddr, esock_atom_undefined) != 0) { + keys[idx] = esock_atom_addr; + vals[idx] = eaddr; + idx++; + } else { + len--; + } + + /* *** Netmask (can be 'undefined' = NULL) *** */ + NDBG( ("NET", "make_ifaddrs -> netmask: %T\r\n", enetmask) ); + if (COMPARE(enetmask, esock_atom_undefined) != 0) { + keys[idx] = atom_netmask; + vals[idx] = enetmask; + idx++; + } else { + len--; + } + + /* *** Netmask (can be 'undefined' = NULL) *** */ + NDBG( ("NET", "make_ifaddrs -> ifu: %T, %T\r\n", eifu_key, eifu_value) ); + if ((COMPARE(eifu_key, esock_atom_undefined) != 0) && + (COMPARE(eifu_value, esock_atom_undefined) != 0)) { + keys[idx] = eifu_key; + vals[idx] = eifu_value; + idx++; + } else { + len--; + } + + /* *** Data (can be 'undefined' = NULL) *** */ + NDBG( ("NET", "make_ifaddrs -> data: %T\r\n", edata) ); + if (COMPARE(edata, esock_atom_undefined) != 0) { + keys[idx] = esock_atom_data; + vals[idx] = edata; + idx++; + } else { + len--; + } + + NDBG( ("NET", "make_ifaddrs -> construct ifa with:" + "\r\n len: %d" + "\r\n" + ) ); + if (!MKMA(env, keys, vals, len, eifAddrs)) { + NDBG( ("NET", "make_ifaddrs -> " + "failed contructing interface adress map\r\n") ); + *eifAddrs = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } else { + return NULL; + } +} + +#endif // HAVE_GETIFADDRS + + +/* ---------------------------------------------------------------------- * nif_if_name2index * * Description: @@ -1470,7 +2004,7 @@ ERL_NIF_TERM encode_address_info(ErlNifEnv* env, if (make_address_info(env, fam, type, proto, addr, &addrInfo) == NULL) return addrInfo; else - return esock_atom_undefined; // We should to better... + return esock_atom_undefined; // We should do better... } @@ -1561,6 +2095,106 @@ char* make_address_info(ErlNifEnv* env, + + +#ifdef HAVE_SETNS +/* We should really have another API, so that we can return errno... */ + +/* *** change network namespace *** + * Retreive the current namespace and set the new. + * Return result and previous namespace if successfull. + */ +#if !defined(__WIN32__) +static +BOOLEAN_T change_network_namespace(char* netns, int* cns, int* err) +{ + int save_errno; + int current_ns = 0; + int new_ns = 0; + + NDBG( ("NET", "change_network_namespace -> entry with" + "\r\n new ns: %s", netns) ); + + if (netns != NULL) { + current_ns = open("/proc/self/ns/net", O_RDONLY); + if (current_ns == -1) { + *cns = current_ns; + *err = get_errno(); + return FALSE; + } + new_ns = open(netns, O_RDONLY); + if (new_ns == -1) { + save_errno = get_errno(); + while (close(current_ns) == -1 && + get_errno() == EINTR); + *cns = -1; + *err = save_errno; + return FALSE; + } + if (setns(new_ns, CLONE_NEWNET) != 0) { + save_errno = get_errno(); + while ((close(new_ns) == -1) && + (get_errno() == EINTR)); + while ((close(current_ns) == -1) && + (get_errno() == EINTR)); + *cns = -1; + *err = save_errno; + return FALSE; + } else { + while ((close(new_ns) == -1) && + (get_errno() == EINTR)); + *cns = current_ns; + *err = 0; + return TRUE; + } + } else { + *cns = -1; + *err = 0; + return TRUE; + } +} + + +/* *** restore network namespace *** + * Restore the previous namespace (see above). + */ +static +BOOLEAN_T restore_network_namespace(int ns, int* err) +{ + int save_errno; + + NDBG( ("NET", "restore_network_namespace -> entry with" + "\r\n ns: %d", ns) ); + + if (ns != -1) { + if (setns(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 namespace. + * Can this even happen? Should the emulator be aborted? + */ + save_errno = get_errno(); + while (close(ns) == -1 && + get_errno() == EINTR); + *err = save_errno; + return FALSE; + } else { + while (close(ns) == -1 && + get_errno() == EINTR); + *err = 0; + return TRUE; + } + } + + *err = 0; + return TRUE; +} +#endif // if !defined(__WIN32__) +#endif // ifdef HAVE_SETNS + + + + /* ---------------------------------------------------------------------- * C a l l b a c k F u n c t i o n s * ---------------------------------------------------------------------- @@ -1626,6 +2260,8 @@ ErlNifFunc net_funcs[] = /* address and name translation in protocol-independent manner */ {"nif_getnameinfo", 2, nif_getnameinfo, 0}, {"nif_getaddrinfo", 3, nif_getaddrinfo, 0}, + + {"nif_getifaddrs", 1, nif_getifaddrs, ERL_NIF_DIRTY_JOB_IO_BOUND}, /* Network interface (name and/or index) functions */ {"nif_if_name2index", 1, nif_if_name2index, 0}, diff --git a/erts/emulator/nifs/common/socket_int.h b/erts/emulator/nifs/common/socket_int.h index 93b196a618..21427b0ae3 100644 --- a/erts/emulator/nifs/common/socket_int.h +++ b/erts/emulator/nifs/common/socket_int.h @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 2018-2018. All Rights Reserved. + * Copyright Ericsson AB 2018-2019. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,6 +60,10 @@ #include <sys/un.h> #endif +#ifdef HAVE_NETPACKET_PACKET_H +#include <netpacket/packet.h> +#endif + #endif #include <erl_nif.h> @@ -82,6 +86,11 @@ typedef union { struct sockaddr_un un; #endif + /* Link Layer sockaddr (used for address family PACKET) */ +#if defined(HAVE_NETPACKET_PACKET_H) && defined(AF_PACKET) + struct sockaddr_ll ll; +#endif + } ESockAddress; @@ -167,15 +176,18 @@ typedef unsigned int BOOLEAN_T; GLOBAL_ATOM_DEF(faith); \ GLOBAL_ATOM_DEF(false); \ GLOBAL_ATOM_DEF(family); \ + GLOBAL_ATOM_DEF(fastroute); \ GLOBAL_ATOM_DEF(flags); \ GLOBAL_ATOM_DEF(flowinfo); \ GLOBAL_ATOM_DEF(fragment_interleave); \ GLOBAL_ATOM_DEF(freebind); \ GLOBAL_ATOM_DEF(get_peer_addr_info); \ + GLOBAL_ATOM_DEF(hatype); \ GLOBAL_ATOM_DEF(hdrincl); \ GLOBAL_ATOM_DEF(hmac_ident); \ GLOBAL_ATOM_DEF(hoplimit); \ GLOBAL_ATOM_DEF(hopopts); \ + GLOBAL_ATOM_DEF(host); \ GLOBAL_ATOM_DEF(icmp); \ GLOBAL_ATOM_DEF(icmp6); \ GLOBAL_ATOM_DEF(ifindex); \ @@ -212,6 +224,7 @@ typedef unsigned int BOOLEAN_T; GLOBAL_ATOM_DEF(msfilter); \ GLOBAL_ATOM_DEF(mtu); \ GLOBAL_ATOM_DEF(mtu_discover); \ + GLOBAL_ATOM_DEF(multicast); \ GLOBAL_ATOM_DEF(multicast_all); \ GLOBAL_ATOM_DEF(multicast_hops); \ GLOBAL_ATOM_DEF(multicast_if); \ @@ -229,6 +242,9 @@ typedef unsigned int BOOLEAN_T; GLOBAL_ATOM_DEF(oobinline); \ GLOBAL_ATOM_DEF(options); \ GLOBAL_ATOM_DEF(origdstaddr); \ + GLOBAL_ATOM_DEF(otherhost); \ + GLOBAL_ATOM_DEF(outgoing); \ + GLOBAL_ATOM_DEF(packet); \ GLOBAL_ATOM_DEF(partial_delivery_point); \ GLOBAL_ATOM_DEF(passcred); \ GLOBAL_ATOM_DEF(path); \ @@ -239,6 +255,7 @@ typedef unsigned int BOOLEAN_T; GLOBAL_ATOM_DEF(peer_auth_chunks); \ GLOBAL_ATOM_DEF(pktinfo); \ GLOBAL_ATOM_DEF(pktoptions); \ + GLOBAL_ATOM_DEF(pkttype); \ GLOBAL_ATOM_DEF(port); \ GLOBAL_ATOM_DEF(portrange); \ GLOBAL_ATOM_DEF(primary_addr); \ diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c index f8a7792c68..e36cd38ece 100644 --- a/erts/emulator/nifs/common/socket_nif.c +++ b/erts/emulator/nifs/common/socket_nif.c @@ -2809,15 +2809,18 @@ static char str_exsend[] = "exsend"; // failed send GLOBAL_ATOM_DECL(faith); \ GLOBAL_ATOM_DECL(false); \ GLOBAL_ATOM_DECL(family); \ + GLOBAL_ATOM_DECL(fastroute); \ GLOBAL_ATOM_DECL(flags); \ GLOBAL_ATOM_DECL(flowinfo); \ GLOBAL_ATOM_DECL(fragment_interleave); \ GLOBAL_ATOM_DECL(freebind); \ GLOBAL_ATOM_DECL(get_peer_addr_info); \ + GLOBAL_ATOM_DECL(hatype); \ GLOBAL_ATOM_DECL(hdrincl); \ GLOBAL_ATOM_DECL(hmac_ident); \ GLOBAL_ATOM_DECL(hoplimit); \ GLOBAL_ATOM_DECL(hopopts); \ + GLOBAL_ATOM_DECL(host); \ GLOBAL_ATOM_DECL(icmp); \ GLOBAL_ATOM_DECL(icmp6); \ GLOBAL_ATOM_DECL(ifindex); \ @@ -2854,6 +2857,7 @@ static char str_exsend[] = "exsend"; // failed send GLOBAL_ATOM_DECL(msfilter); \ GLOBAL_ATOM_DECL(mtu); \ GLOBAL_ATOM_DECL(mtu_discover); \ + GLOBAL_ATOM_DECL(multicast); \ GLOBAL_ATOM_DECL(multicast_all); \ GLOBAL_ATOM_DECL(multicast_hops); \ GLOBAL_ATOM_DECL(multicast_if); \ @@ -2871,6 +2875,9 @@ static char str_exsend[] = "exsend"; // failed send GLOBAL_ATOM_DECL(oobinline); \ GLOBAL_ATOM_DECL(options); \ GLOBAL_ATOM_DECL(origdstaddr); \ + GLOBAL_ATOM_DECL(otherhost); \ + GLOBAL_ATOM_DECL(outgoing); \ + GLOBAL_ATOM_DECL(packet); \ GLOBAL_ATOM_DECL(partial_delivery_point); \ GLOBAL_ATOM_DECL(passcred); \ GLOBAL_ATOM_DECL(path); \ @@ -2881,6 +2888,7 @@ static char str_exsend[] = "exsend"; // failed send GLOBAL_ATOM_DECL(peer_auth_chunks); \ GLOBAL_ATOM_DECL(pktinfo); \ GLOBAL_ATOM_DECL(pktoptions); \ + GLOBAL_ATOM_DECL(pkttype); \ GLOBAL_ATOM_DECL(port); \ GLOBAL_ATOM_DECL(portrange); \ GLOBAL_ATOM_DECL(primary_addr); \ diff --git a/erts/emulator/nifs/common/socket_util.c b/erts/emulator/nifs/common/socket_util.c index 9496757b77..6e60da2f07 100644 --- a/erts/emulator/nifs/common/socket_util.c +++ b/erts/emulator/nifs/common/socket_util.c @@ -39,6 +39,7 @@ #include "config.h" #endif + /* We don't have a "debug flag" to check here, so we * should use the compile debug flag, whatever that is... */ @@ -73,6 +74,13 @@ static char* make_sockaddr_in6(ErlNifEnv* env, static char* make_sockaddr_un(ErlNifEnv* env, ERL_NIF_TERM path, ERL_NIF_TERM* sa); +static char* make_sockaddr_ll(ErlNifEnv* env, + ERL_NIF_TERM proto, + ERL_NIF_TERM ifindex, + ERL_NIF_TERM hatype, + ERL_NIF_TERM pkttype, + ERL_NIF_TERM addr, + ERL_NIF_TERM* sa); /* +++ esock_encode_iov +++ @@ -310,6 +318,12 @@ char* esock_encode_sockaddr(ErlNifEnv* env, break; #endif +#ifdef HAVE_NETPACKET_PACKET_H + case AF_PACKET: + xres = esock_encode_sockaddr_ll(env, &sockAddrP->ll, addrLen, eSockAddr); + break; +#endif + default: *eSockAddr = esock_atom_undefined; xres = ESOCK_STR_EAFNOSUPPORT; @@ -717,6 +731,65 @@ char* esock_encode_sockaddr_un(ErlNifEnv* env, +/* +++ esock_encode_sockaddr_ll +++ + * + * Encode a PACKET address - sockaddr_ll (link layer). In erlang its + * represented as a map, which has a specific set of attributes + * (beside the mandatory family attribute, which is "inherited" from + * the "sockaddr" type): + * + * protocol: integer() (should be an atom really) + * ifindex: integer() + * hatype: integer() (should be an atom really) + * pkttype: integer() (should be an atom really) + * addr: list() (should be something usefull...) + * + */ + +#ifdef HAVE_NETPACKET_PACKET_H +extern +char* esock_encode_sockaddr_ll(ErlNifEnv* env, + struct sockaddr_ll* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr) +{ + ERL_NIF_TERM eProto, eIfIdx, eHaType, ePktType, eAddr; + char* xres; + + if (addrLen >= sizeof(struct sockaddr_ll)) { + + /* protocol - the standard ethernet protocol type */ + esock_encode_packet_protocol(env, ntohs(sockAddrP->sll_protocol), &eProto); + + /* ifindex - the interface index of the interface */ + eIfIdx = MKI(env, sockAddrP->sll_ifindex); + + /* hatype - is an ARP (hardware) type */ + esock_encode_packet_hatype(env, sockAddrP->sll_hatype, &eHaType); + + /* pkttype - the packet type */ + esock_encode_packet_pkttype(env, sockAddrP->sll_pkttype, &ePktType); + + /* addr - the physical-layer (e.g., IEEE 802.3) address */ + esock_encode_packet_addr(env, + sockAddrP->sll_halen, sockAddrP->sll_addr, + &eAddr); + + xres = make_sockaddr_ll(env, + eProto, eIfIdx, eHaType, ePktType, eAddr, + eSockAddr); + + } else { + *eSockAddr = esock_atom_undefined; + xres = ESOCK_STR_EINVAL; + } + + return xres; +} +#endif + + + /* +++ esock_decode_ip4_address +++ * * Decode a IPv4 address. This can be three things: @@ -1293,6 +1366,142 @@ char* esock_decode_protocol(ErlNifEnv* env, +/* +++ esock_encode_packet_protocol +++ + * + * Encode the Link Layer sockaddr protocol. + * + * Currently we just represent this as an unsigned int. + */ +extern +void esock_encode_packet_protocol(ErlNifEnv* env, + unsigned short protocol, + ERL_NIF_TERM* eProtocol) +{ + *eProtocol = MKUI(env, protocol); +} + + +/* +++ esock_encode_packet_hatype +++ + * + * Encode the Link Layer sockaddr hatype. + * + * Currently we just represent this as an unsigned int. + */ +extern +void esock_encode_packet_hatype(ErlNifEnv* env, + unsigned short hatype, + ERL_NIF_TERM* eHaType) +{ + *eHaType = MKUI(env, hatype); +} + + +/* +++ esock_encode_packet_pkttype +++ + * + * Encode the Link Layer sockaddr pkttype. + * + * PACKET_HOST => host + * PACKET_BROADCAST => broadcast + * PACKET_MULTICAST => multicast + * PACKET_OTHERHOST => otherhost + * PACKET_OUTGOING => outgoing + * PACKET_LOOPBACK => loopback + * PACKET_USER => user + * PACKET_KERNEL => kernel + * + */ +extern +void esock_encode_packet_pkttype(ErlNifEnv* env, + unsigned short pkttype, + ERL_NIF_TERM* ePktType) +{ + switch (pkttype) { +#if defined(PACKET_HOST) + case PACKET_HOST: + *ePktType = esock_atom_host; + break; +#endif + +#if defined(PACKET_BROADCAST) + case PACKET_BROADCAST: + *ePktType = esock_atom_broadcast; + break; +#endif + +#if defined(PACKET_MULTICAST) + case PACKET_MULTICAST: + *ePktType = esock_atom_multicast; + break; +#endif + +#if defined(PACKET_OTHERHOST) + case PACKET_OTHERHOST: + *ePktType = esock_atom_otherhost; + break; +#endif + +#if defined(PACKET_OUTGOING) + case PACKET_OUTGOING: + *ePktType = esock_atom_outgoing; + break; +#endif + +#if defined(PACKET_LOOPBACK) + case PACKET_LOOPBACK: + *ePktType = esock_atom_loopback; + break; +#endif + +#if defined(PACKET_USER) + case PACKET_USER: + *ePktType = esock_atom_user; + break; +#endif + +#if defined(PACKET_KERNEL) + case PACKET_KERNEL: + *ePktType = esock_atom_kernel; + break; +#endif + +#if defined(PACKET_FASTROUTE) + case PACKET_FASTROUTE: + *ePktType = esock_atom_fastroute; + break; +#endif + + default: + *ePktType = MKUI(env, pkttype); + break; + } + +} + + + +/* +++ esock_encode_packet_addr +++ + * + * Encode the Link Layer sockaddr address. + * + */ +extern +void esock_encode_packet_addr(ErlNifEnv* env, + unsigned char len, + unsigned char* addr, + ERL_NIF_TERM* eAddr) +{ + ERL_NIF_TERM array[len]; + unsigned char i; + + for (i = 0; i < len; i++) { + array[i] = MKUI(env, addr[i]); + } + + *eAddr = MKTA(env, array, len); +} + + + /* +++ esock_decode_bufsz +++ * * Decode an buffer size. The size of a buffer is: @@ -1737,3 +1946,36 @@ char* make_sockaddr_un(ErlNifEnv* env, } +/* Construct the Link Layer socket address */ +#ifdef HAVE_NETPACKET_PACKET_H +static +char* make_sockaddr_ll(ErlNifEnv* env, + ERL_NIF_TERM proto, + ERL_NIF_TERM ifindex, + ERL_NIF_TERM hatype, + ERL_NIF_TERM pkttype, + ERL_NIF_TERM addr, + ERL_NIF_TERM* sa) +{ + ERL_NIF_TERM keys[] = {esock_atom_family, + esock_atom_protocol, + esock_atom_ifindex, + esock_atom_hatype, + esock_atom_pkttype, + esock_atom_addr}; + ERL_NIF_TERM vals[] = {esock_atom_packet, proto, ifindex, + hatype, pkttype, addr}; + unsigned int numKeys = sizeof(keys) / sizeof(ERL_NIF_TERM); + unsigned int numVals = sizeof(vals) / sizeof(ERL_NIF_TERM); + + ESOCK_ASSERT( (numKeys == numVals) ); + + if (!MKMA(env, keys, vals, numKeys, sa)) { + *sa = esock_atom_undefined; + return ESOCK_STR_EINVAL; + } else { + return NULL; + } +} +#endif + diff --git a/erts/emulator/nifs/common/socket_util.h b/erts/emulator/nifs/common/socket_util.h index 725dac6c95..976d94cd8c 100644 --- a/erts/emulator/nifs/common/socket_util.h +++ b/erts/emulator/nifs/common/socket_util.h @@ -104,6 +104,14 @@ char* esock_encode_sockaddr_un(ErlNifEnv* env, ERL_NIF_TERM* eSockAddr); #endif +#ifdef HAVE_NETPACKET_PACKET_H +extern +char* esock_encode_sockaddr_ll(ErlNifEnv* env, + struct sockaddr_ll* sockAddrP, + unsigned int addrLen, + ERL_NIF_TERM* eSockAddr); +#endif + extern char* esock_decode_ip4_address(ErlNifEnv* env, ERL_NIF_TERM eAddr, @@ -158,6 +166,24 @@ char* esock_encode_protocol(ErlNifEnv* env, ERL_NIF_TERM* eProtocol); extern +void esock_encode_packet_protocol(ErlNifEnv* env, + unsigned short protocol, + ERL_NIF_TERM* eProtocol); +extern +void esock_encode_packet_hatype(ErlNifEnv* env, + unsigned short hatype, + ERL_NIF_TERM* eHaType); +extern +void esock_encode_packet_pkttype(ErlNifEnv* env, + unsigned short pkttype, + ERL_NIF_TERM* ePktType); +extern +void esock_encode_packet_addr(ErlNifEnv* env, + unsigned char len, + unsigned char* addr, + ERL_NIF_TERM* eAddr); + +extern char* esock_decode_bufsz(ErlNifEnv* env, ERL_NIF_TERM eVal, size_t defSz, diff --git a/erts/preloaded/ebin/prim_net.beam b/erts/preloaded/ebin/prim_net.beam Binary files differindex cb131815b1..bbaf916152 100644 --- a/erts/preloaded/ebin/prim_net.beam +++ b/erts/preloaded/ebin/prim_net.beam diff --git a/erts/preloaded/src/prim_net.erl b/erts/preloaded/src/prim_net.erl index 99e00ee2e7..fcd528bdaa 100644 --- a/erts/preloaded/src/prim_net.erl +++ b/erts/preloaded/src/prim_net.erl @@ -33,6 +33,7 @@ gethostname/0, getnameinfo/2, getaddrinfo/2, + getifaddrs/1, if_name2index/1, if_index2name/1, @@ -41,18 +42,37 @@ -export_type([ address_info/0, + ifaddrs/0, name_info/0, - name_info_flags/0, + ifaddrs_flag/0, + ifaddrs_flags/0, + name_info_flag/0, name_info_flag_ext/0, + name_info_flags/0, network_interface_name/0, network_interface_index/0 ]). --type name_info_flags() :: [name_info_flag()|name_info_flag_ext()]. +-type ifaddrs_flag() :: up | broadcast | debug | loopback | pointopoint | + notrailers | running | noarp | promisc | master | slave | + multicast | portsel | automedia | dynamic. +-type ifaddrs_flags() :: [ifaddrs_flag()]. + +%% Note that not all of these fields are mandatory. +%% Actually there are (error) cases when only the name will be included. +%% And broadaddr and dstaddr are mutually exclusive! + +-type ifaddrs() :: #{name := string(), + flags := ifaddrs_flags(), + addr := socket:sockaddr(), + netmask := socket:sockaddr(), + broadaddr := socket:sockaddr(), + dstaddr := socket:sockaddr()}. + -type name_info_flag() :: namereqd | dgram | nofqdn | @@ -62,6 +82,7 @@ %% (as they are deprecated by later version of gcc): %% idn_allow_unassigned | idn_use_std3_ascii_rules. -type name_info_flag_ext() :: idn. +-type name_info_flags() :: [name_info_flag()|name_info_flag_ext()]. -type name_info() :: #{host := string(), service := string()}. -type address_info() :: #{family := socket:domain(), @@ -177,6 +198,21 @@ getaddrinfo(Host, Service) %% =========================================================================== %% +%% getifaddrs - Get interface addresses +%% + +-spec getifaddrs(Extra) -> {ok, IfInfo} | {error, Reason} when + Extra :: map(), + IfInfo :: [ifaddrs()], + Reason :: term(). + +getifaddrs(Extra) when is_map(Extra) -> + nif_getifaddrs(Extra). + + + +%% =========================================================================== +%% %% if_name2index - Mappings between network interface names and indexes: %% name -> idx %% @@ -279,6 +315,9 @@ nif_getnameinfo(_Addr, _Flags) -> nif_getaddrinfo(_Host, _Service, _Hints) -> erlang:nif_error(undef). +nif_getifaddrs(_Extra) -> + erlang:nif_error(undef). + nif_if_name2index(_Name) -> erlang:nif_error(undef). |