diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..de9b652 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +elixir 1.16.3-otp-26 +erlang 26.2.5 diff --git a/Makefile b/Makefile index e345628..62430e3 100644 --- a/Makefile +++ b/Makefile @@ -50,6 +50,9 @@ else CFLAGS += -std=gnu99 endif +#We need this for netinet/in.h IN6_* macros +CFLAGS += -D_GNU_SOURCE + # If not cross-compiling, then run sudo by default ifeq ($(origin CROSSCOMPILE), undefined) SUDO_ASKPASS ?= /usr/bin/ssh-askpass diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..05163a8 --- /dev/null +++ b/config/config.exs @@ -0,0 +1,9 @@ +import Config + +#config :nerves_network_interface, :managed_interfaces, +# ["eth0", "ens38"] + +# Other possible configurations: +# 1. Manage all available interfaces - explicitly said implicit equivalen of no :managed interfaces provided +# config :nerves_network_interface, :managed_interfaces, +# "all" diff --git a/lib/nerves_network_interface.ex b/lib/nerves_network_interface.ex index 3d2870f..1e52111 100644 --- a/lib/nerves_network_interface.ex +++ b/lib/nerves_network_interface.ex @@ -14,6 +14,8 @@ defmodule Nerves.NetworkInterface do + require Logger + @moduledoc """ This module exposes a simplified view of Linux network configuration to applications. @@ -34,6 +36,68 @@ defmodule Nerves.NetworkInterface do ensure that the port process is running as a privileged user. """ + @type interface_name :: String.t + @type registration :: + {:ok, pid} | + {:error, {:already_registered, pid}} + + @type operstate :: + :unknown + | :notpresent + | :down + | :lowerlayerdown + | :testing + | :dormant + | :up + + @type ipv4_address :: String.t() + @type ipv6_address :: String.t() + + @type dhcpv6_mode :: + :stateful + | :stateless + + @type ifsettings_ipv4 :: %{ + ifname: Nerves.NetworkInterface.interface_name(), + domain: String.t(), + ipv4_address: ipv4_address(), + ipv4_broadcast: ipv4_address(), + ipv4_gateway: ipv4_address(), + ipv4_subnet_mask: ipv4_address(), + nameservers: list(ipv4_address()) + } + + @type ifsettings_ipv6 :: %{ + ifname: Nerves.NetworkInterface.interface_name(), + ipv6_domain: String.t(), + ipv6_address: ipv6_address(), + ipv6_nameservers: list(ipv6_address()), + old_ipv6_address: ipv6_address(), + dhcpv6_mode: dhcpv6_mode() + } + + @type ifstatus :: %{ + ifname: Nerves.NetworkInterface.interface_name(), + index: integer(), + "is_all-multicast": boolean(), + is_broadcast: boolean(), + is_lower_up: boolean(), + is_multicast: boolean(), + is_running: boolean(), + is_up: boolean(), + mac_address: String.t(), + mac_broadcast: String.t(), + mtu: integer(), + operstate: operstate(), + type: :ethernet | :other + } + + @type ifevent :: + ifstatus() + | ifsettings_ipv4() + | ifsettings_ipv6() + + @doc """ Return the list of network interfaces on this machine. """ @@ -93,4 +157,32 @@ defmodule Nerves.NetworkInterface do Returns `:ok` on success or `{:error, reason}` if an error occurs. """ defdelegate setup(ifname, options), to: Nerves.NetworkInterface.Worker + + @doc """ + Register for Nerves.NetworkInterface events on a specific interface + + The calling process is the process that will be registered for + all events. The events can be handled by implementing a `handle_info\2`. + + `def handle_info({Nerves.NetworkInterface, :ifchanged, ifstate} = event, state)` + + Use :all to register for events from all interfaces. + + The registration registers for messages dispatched out of `Registry`. + + For information on how `Registry` works please see that module's + documentation. + """ + @spec register(:all | interface_name) :: + registration | + [registration] | + [] + def register(:all) do + Enum.each(interfaces(), ®ister/1) + end + + def register(ifname) do + Logger.debug("Registering for notifications for #{ifname}...") + Registry.register(Nerves.NetworkInterface, ifname, []) + end end diff --git a/lib/network_interface/application.ex b/lib/network_interface/application.ex index 94839e9..0f79b4a 100644 --- a/lib/network_interface/application.ex +++ b/lib/network_interface/application.ex @@ -4,11 +4,10 @@ defmodule Nerves.NetworkInterface.Application do use Application def start(_type, _args) do - import Supervisor.Spec, warn: false children = [ - supervisor(Registry, [:duplicate, Nerves.NetworkInterface]), - worker(Nerves.NetworkInterface.Worker, []), + %{id: Registry, start: {Registry, :start_link, [[keys: :duplicate, name: Nerves.NetworkInterface]]}}, + %{id: Nerves.NetworkInterface.Worker, start: {Nerves.NetworkInterface.Worker, :start_link, []}} ] opts = [strategy: :rest_for_one, name: Nerves.NervesInterface.Supervisor] diff --git a/lib/network_interface/worker.ex b/lib/network_interface/worker.ex index bd732bd..9890f01 100644 --- a/lib/network_interface/worker.ex +++ b/lib/network_interface/worker.ex @@ -71,6 +71,7 @@ defmodule Nerves.NetworkInterface.Worker do is_broadcast: boolean, is_lower_up: boolean, is_multicast: boolean, + "is_all-multicast": boolean, is_up: boolean, is_running: boolean, mac_address: mac_address, @@ -118,15 +119,43 @@ defmodule Nerves.NetworkInterface.Worker do end def init([]) do - Logger.info "Start Network Interface Worker" + Logger.warning "Start Network Interface Worker" executable = :code.priv_dir(:nerves_network_interface) ++ '/netif' - port = Port.open({:spawn_executable, executable}, - [{:packet, 2}, :use_stdio, :binary]) + port = Port.open({:spawn_executable, to_charlist(MuonTrap.muontrap_path())}, + [{:args, ["--", executable]}, {:packet, 2}, :use_stdio, :binary]) { :ok, %Nerves.NetworkInterface.Worker{port: port} } end + #Returns intersection of lists a and b + defp intersect(a, b), do: a -- (a -- b) + + # Returns list of interfaces to be managed by Nerves.NetworkInterface and Nerves.Network modules + # By default this is list of ALL network interfaces available in the system. It can be reduced + # by specifying a list of interfaces we want to be managed by Nerves.Network sub-system in the + # .../config/config.exs file. + defp get_managed_interfaces(available_interfaces) do + managed_interfaces = Application.get_env(:nerves_network_interface, :managed_interfaces, []) + Logger.debug "#{__MODULE__}: managed_interfaces = #{inspect managed_interfaces}" + + case managed_interfaces do + "all" -> available_interfaces + nil -> available_interfaces + [] -> available_interfaces + _ -> managed_interfaces + end + end + + def handle_call(:all_interfaces, _from, state) do + available_interfaces = call_port(state, :interfaces, []) + {:reply, available_interfaces, state } + end + def handle_call(:interfaces, _from, state) do - response = call_port(state, :interfaces, []) + available_interfaces = call_port(state, :interfaces, []) + response = + get_managed_interfaces(available_interfaces) + |> intersect(available_interfaces) + Logger.debug "#{__MODULE__}: response: #{inspect response}" {:reply, response, state } end def handle_call({:status, ifname}, _from, state) do @@ -142,6 +171,8 @@ defmodule Nerves.NetworkInterface.Worker do {:reply, response, state } end def handle_call({:setup, ifname, options}, _from, state) do + Logger.debug(":setup #{ifname} options = #{inspect options}") + response = call_port(state, :setup, {ifname, options}) {:reply, response, state } end @@ -154,14 +185,25 @@ defmodule Nerves.NetworkInterface.Worker do {:stop, :normal, state} end - def handle_info({_, {:data, <>}}, state) do - {notif, data} = :erlang.binary_to_term(message) - Logger.info "nerves_network_interface received #{inspect notif} and #{inspect data}" + def dispatch(notif, data) do + Logger.debug "nerves_network_interface received #{inspect notif} and #{inspect data}" Registry.dispatch(Nerves.NetworkInterface, data.ifname, fn entries -> for {pid, _} <- entries do + Logger.debug("Dispatching for pid = #{inspect pid} notif = #{inspect notif} data = #{inspect data}") + Logger.debug("Process info for pid = #{inspect pid}: #{inspect Process.info(pid)}") + send(pid, {Nerves.NetworkInterface, notif, data}) end end) + end + + def handle_info({_, _input = {:data, <>}}, state) do + try do + {notif, data} = :erlang.binary_to_term(message) + dispatch(notif, data) + rescue + e -> Logger.error("Error converting to term: #{inspect e}!") + end {:noreply, state} end diff --git a/mix.exs b/mix.exs index dede0b2..e822bbc 100644 --- a/mix.exs +++ b/mix.exs @@ -41,6 +41,10 @@ defmodule Nerves.NetworkInterface.Mixfile do defp deps do [{:dialyxir, ">= 0.0.0", only: [:dev, :test]}, {:elixir_make, "~> 0.4", runtime: false}, - {:ex_doc, "~> 0.11", only: :dev}] + {:ex_doc, "~> 0.11", only: :dev}, + + ## netif doesn't seem to work with later versions of muontrap + {:muontrap, "~> 1.0.0"} + ] end end diff --git a/mix.lock b/mix.lock index f9f2441..f6035f4 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,13 @@ -%{"dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [], [], "hexpm"}, - "earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [:mix], [], "hexpm"}, - "elixir_make": {:hex, :elixir_make, "0.4.0", "992f38fabe705bb45821a728f20914c554b276838433349d4f2341f7a687cddf", [:mix], [], "hexpm"}, - "ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}} +%{ + "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, + "earmark": {:hex, :earmark, "1.2.3", "206eb2e2ac1a794aa5256f3982de7a76bf4579ff91cb28d0e17ea2c9491e46a4", [:mix], [], "hexpm", "3b1dcad3067985dd8618c38399a8ee9c4e652d52a17a4aae7a6d6fc4fcc24856"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, + "elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"}, + "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "ex_doc": {:hex, :ex_doc, "0.34.0", "ab95e0775db3df71d30cf8d78728dd9261c355c81382bcd4cefdc74610bef13e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "60734fb4c1353f270c3286df4a0d51e65a2c1d9fba66af3940847cc65a8066d7"}, + "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, + "muontrap": {:hex, :muontrap, "1.0.0", "53a05c37f71cc5070aaa0858a774ae1f500160b7186a70565521a14ef7843c5a", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "0d3cd6e335986f9c2af1b61f583375b0f0d91cea95b7ec7bc720f330b4dc9b49"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, +} diff --git a/src/debug.h b/src/debug.h new file mode 100644 index 0000000..4befc51 --- /dev/null +++ b/src/debug.h @@ -0,0 +1,45 @@ +/**************************************************************** + * Copyright (C) 2021 Schneider Electric * + ****************************************************************/ + +#if !defined(__DEBUG_H__) +#define __DEBUG_H__ + +//#define DEBUG +//#define INFO +#define WARN +#define ERROR + +#ifdef DEBUG +#define debug(format, ...) fprintf(stderr, "DBG: "format, __VA_ARGS__); fprintf(stderr, "\r\n") +#define debugf(string) fprintf(stderr, string); fprintf(stderr, "\r\n") +#else +#define debug(format, ...) +#define debugf(string) +#endif + +#ifdef INFO +#define info(format, ...) fprintf(stderr, "INFO: "format, __VA_ARGS__); fprintf(stderr, "\r\n") +#define infof(string) fprintf(stderr, string); fprintf(stderr, "\r\n") +#else +#define info(format, ...) +#define infof(string) +#endif + +#ifdef WARN +#define warn(format, ...) fprintf(stderr, "WARN: "format, __VA_ARGS__); fprintf(stderr, "\r\n") +#define warnf(string) fprintf(stderr, string); fprintf(stderr, "\r\n") +#else +#define warn(format, ...) +#define warnf(string) +#endif + +#ifdef ERROR +#define error(format, ...) fprintf(stderr, "ERROR:"format, __VA_ARGS__); fprintf(stderr, "\r\n") +#define errorf(string) fprintf(stderr, string); fprintf(stderr, "\r\n") +#else +#define error(format, ...) +#define errorf(string) +#endif + +#endif diff --git a/src/erlcmd.c b/src/erlcmd.c index b781ec5..e2dd570 100644 --- a/src/erlcmd.c +++ b/src/erlcmd.c @@ -250,8 +250,21 @@ int erlcmd_encode_errno_error(char *buf, int *index, int err) case EROFS: reason = "erofs"; break; case EMLINK: reason = "emlink"; break; case EPIPE: reason = "epipe"; break; + case ENETDOWN: reason = "enetdown"; break; + case ENETUNREACH: reason = "enetunreach"; break; + case ENETRESET: reason = "enetreset"; break; + case ECONNABORTED: reason = "econnaborted"; break; + case ECONNRESET: reason = "econnreset"; break; case EADDRNOTAVAIL: reason = "eaddrnotavail"; break; - default: reason = "unknown"; break; + case ENOBUFS: reason = "enobufs"; break; + case EISCONN: reason = "eisconn"; break; + case ENOTCONN: reason = "enotconn"; break; + case ETOOMANYREFS: reason = "etoomanyrefs"; break; + case ETIMEDOUT: reason = "etimedout"; break; + case ECONNREFUSED: reason = "econnrefused"; break; + case EHOSTDOWN: reason = "ehostdown"; break; + case EHOSTUNREACH: reason = "ehostunreach"; break; + default: reason = "unknown"; break; } return erlcmd_encode_error_tuple(buf, index, reason); } diff --git a/src/erlcmd.h b/src/erlcmd.h index 226adf9..df59092 100644 --- a/src/erlcmd.h +++ b/src/erlcmd.h @@ -24,7 +24,11 @@ /* * Erlang request/response processing */ -#define ERLCMD_BUF_SIZE 1024 +/* + * The IPv6 addresses' is a variable length list - th 16kB is enough to hold + * net.ipv6.conf.eth0.max_addresses=16. + */ +#define ERLCMD_BUF_SIZE 1024*16 struct erlcmd { char buffer[ERLCMD_BUF_SIZE]; diff --git a/src/netif.c b/src/netif.c index 9581199..b527529 100644 --- a/src/netif.c +++ b/src/netif.c @@ -14,6 +14,11 @@ * limitations under the License. */ +/**************************************************************** + * Copyright (C) 2021 Schneider Electric * + ****************************************************************/ + + #include #include #include @@ -27,9 +32,15 @@ #include #include #include +#include +#include #include #include #include +#include +#include +#include +#include // In Ubuntu 16.04, it seems that the new compat logic handling is preventing // IFF_LOWER_UP from being defined properly. It looks like a bug, so define it @@ -39,15 +50,11 @@ #define WORKAROUND_IFF_LOWER_UP (0x10000) #define MACADDR_STR_LEN 18 // aa:bb:cc:dd:ee:ff and a null terminator +#define MAX_PREFIX_LEN 5 // length of "/128" (4-bytes) + length of '\0' termination string #include "erlcmd.h" -//#define DEBUG -#ifdef DEBUG -#define debug(...) do { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\r\n"); } while(0) -#else -#define debug(...) -#endif +#include "debug.h" struct netif { // NETLINK_ROUTE socket information @@ -60,6 +67,9 @@ struct netif { // AF_INET socket for ioctls int inet_fd; + // AF_INET6 socket for ioctls + int inet6_fd; + // Netlink buffering char nlbuf[8192]; // See MNL_SOCKET_BUFFER_SIZE @@ -81,6 +91,11 @@ struct netif { int last_error; }; +struct netif_link_settings { + __u32 speed; + __u8 duplex; +}; + static void netif_init(struct netif *nb) { memset(nb, 0, sizeof(*nb)); @@ -103,6 +118,10 @@ static void netif_init(struct netif *nb) if (nb->inet_fd < 0) err(EXIT_FAILURE, "socket"); + nb->inet6_fd = socket(AF_INET6, SOCK_DGRAM, 0); + if (nb->inet6_fd < 0) + err(EXIT_FAILURE, "socket6"); + nb->seq = 1; } @@ -113,13 +132,20 @@ static void netif_cleanup(struct netif *nb) nb->nl = NULL; } -static void start_response(struct netif *nb) +static inline void start_response(struct netif *nb) { nb->resp_index = sizeof(uint16_t); // Space for payload size nb->resp[nb->resp_index++] = 'r'; // Indicate response ei_encode_version(nb->resp, &nb->resp_index); } +static inline void start_notification(struct netif *nb) +{ + nb->resp_index = sizeof(uint16_t); // Skip over payload size + nb->resp[nb->resp_index++] = 'n'; + ei_encode_version(nb->resp, &nb->resp_index); +} + static void send_response(struct netif *nb) { debug("sending response: %d bytes", nb->resp_index); @@ -203,13 +229,18 @@ static void encode_kv_string(struct netif *nb, const char *key, const char *str) encode_string(nb->resp, &nb->resp_index, str); } -static void encode_kv_macaddr(struct netif *nb, const char *key, const unsigned char *macaddr) +static void encode_kv_atom(struct netif *nb, const char *key, const char *str) { ei_encode_atom(nb->resp, &nb->resp_index, key); + ei_encode_atom(nb->resp, &nb->resp_index, str); +} - char macaddr_str[MACADDR_STR_LEN]; +static void encode_kv_macaddr(struct netif *nb, const char *key, const unsigned char *macaddr) +{ + char macaddr_str[MACADDR_STR_LEN] = {'\0'}; + + ei_encode_atom(nb->resp, &nb->resp_index, key); - // Only handle 6 byte mac addresses (to my knowledge, this is the only case) macaddr_to_string(macaddr, macaddr_str); encode_string(nb->resp, &nb->resp_index, macaddr_str); @@ -233,6 +264,30 @@ static void encode_kv_stats(struct netif *nb, const char *key, struct nlattr *at encode_kv_ulong(nb, "collisions", stats->collisions); } +static void encode_kv_link_settings(struct netif *nb, const char *key, const struct netif_link_settings *ls) +{ + ei_encode_atom(nb->resp, &nb->resp_index, key); + if (ls != NULL) { + ei_encode_map_header(nb->resp, &nb->resp_index, 2); + + encode_kv_ulong(nb, "speed", ls->speed); + + switch (ls->duplex) { + case DUPLEX_HALF: + encode_kv_atom(nb, "duplex", "half"); + break; + case DUPLEX_FULL: + encode_kv_atom(nb, "duplex", "full"); + break; + default: + encode_kv_atom(nb, "duplex", "unknown"); + break; + }; + } else { + ei_encode_map_header(nb->resp, &nb->resp_index, 0); + } +} + static void encode_kv_operstate(struct netif *nb, int operstate) { ei_encode_atom(nb->resp, &nb->resp_index, "operstate"); @@ -252,53 +307,100 @@ static void encode_kv_operstate(struct netif *nb, int operstate) ei_encode_atom(nb->resp, &nb->resp_index, operstate_atom); } +static int ethtool_gset_ioctl(struct netif *nb, const char *ifname, struct netif_link_settings *ls) +{ + struct ethtool_cmd ecmd = {0, }; + struct ifreq ifr = {0, }; + + ecmd.cmd = ETHTOOL_GSET; + + strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); + ifr.ifr_data = (void *) &ecmd; + + if (ioctl(nb->inet_fd, SIOCETHTOOL, &ifr) < 0) { + error("ioctl(0x%04x) failed for getting '%s': %s for %s", SIOCETHTOOL, "ETHTOOL_GSET", strerror(errno), ifname); + nb->last_error = errno; + return -1; + } + + ls->speed = (ecmd.speed_hi << 16) | ecmd.speed; + ls->duplex = ecmd.duplex; + + return 0; +} + static int netif_build_ifinfo(const struct nlmsghdr *nlh, void *data) { - struct netif *nb = (struct netif *) data; - struct nlattr *tb[IFLA_MAX + 1]; - memset(tb, 0, sizeof(tb)); - struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh); - - if (mnl_attr_parse(nlh, sizeof(*ifm), collect_if_attrs, tb) != MNL_CB_OK) { - debug("Error from mnl_attr_parse"); - return MNL_CB_ERROR; - } - - int count = 7; // Number of fields that we always encode - int i; - for (i = 0; i <= IFLA_MAX; i++) - if (tb[i]) - count++; - - ei_encode_map_header(nb->resp, &nb->resp_index, count); - - encode_kv_long(nb, "index", ifm->ifi_index); - - ei_encode_atom(nb->resp, &nb->resp_index, "type"); - ei_encode_atom(nb->resp, &nb->resp_index, ifm->ifi_type == ARPHRD_ETHER ? "ethernet" : "other"); - - encode_kv_bool(nb, "is_up", ifm->ifi_flags & IFF_UP); - encode_kv_bool(nb, "is_broadcast", ifm->ifi_flags & IFF_BROADCAST); - encode_kv_bool(nb, "is_running", ifm->ifi_flags & IFF_RUNNING); - encode_kv_bool(nb, "is_lower_up", ifm->ifi_flags & WORKAROUND_IFF_LOWER_UP); - encode_kv_bool(nb, "is_multicast", ifm->ifi_flags & IFF_MULTICAST); - - if (tb[IFLA_MTU]) - encode_kv_ulong(nb, "mtu", mnl_attr_get_u32(tb[IFLA_MTU])); - if (tb[IFLA_IFNAME]) - encode_kv_string(nb, "ifname", mnl_attr_get_str(tb[IFLA_IFNAME])); - if (tb[IFLA_ADDRESS]) - encode_kv_macaddr(nb, "mac_address", mnl_attr_get_payload(tb[IFLA_ADDRESS])); - if (tb[IFLA_BROADCAST]) - encode_kv_macaddr(nb, "mac_broadcast", mnl_attr_get_payload(tb[IFLA_BROADCAST])); - if (tb[IFLA_LINK]) - encode_kv_ulong(nb, "link", mnl_attr_get_u32(tb[IFLA_LINK])); - if (tb[IFLA_OPERSTATE]) - encode_kv_operstate(nb, mnl_attr_get_u32(tb[IFLA_OPERSTATE])); - if (tb[IFLA_STATS]) - encode_kv_stats(nb, "stats", tb[IFLA_STATS]); + struct netif *nb = (struct netif *) data; + struct nlattr *tb[IFLA_MAX + 1]; + memset(tb, 0, sizeof(tb)); + struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh); + + if (mnl_attr_parse(nlh, sizeof(*ifm), collect_if_attrs, tb) != MNL_CB_OK) { + debugf("Error from mnl_attr_parse"); + return MNL_CB_ERROR; + } + + int count = 9; /* Number of fields that we always encode */ + int i; + + for (i = 0; i <= IFLA_MAX; i++) + if (tb[i]) + count++; + + ei_encode_map_header(nb->resp, &nb->resp_index, count); + + encode_kv_long(nb, "index", ifm->ifi_index); + encode_kv_atom(nb, "type", ifm->ifi_type == ARPHRD_ETHER ? "ethernet" : "other"); + + encode_kv_bool(nb, "is_up", ifm->ifi_flags & IFF_UP); + encode_kv_bool(nb, "is_broadcast", ifm->ifi_flags & IFF_BROADCAST); + encode_kv_bool(nb, "is_running", ifm->ifi_flags & IFF_RUNNING); + encode_kv_bool(nb, "is_lower_up", ifm->ifi_flags & WORKAROUND_IFF_LOWER_UP); + encode_kv_bool(nb, "is_multicast", ifm->ifi_flags & IFF_MULTICAST); + encode_kv_bool(nb, "is_all-multicast", ifm->ifi_flags & IFF_ALLMULTI); + + if (tb[IFLA_MTU]) { + encode_kv_ulong(nb, "mtu", mnl_attr_get_u32(tb[IFLA_MTU])); + } + if (tb[IFLA_IFNAME]) { + encode_kv_string(nb, "ifname", mnl_attr_get_str(tb[IFLA_IFNAME])); + } + if (tb[IFLA_ADDRESS]) { + encode_kv_macaddr(nb, "mac_address", mnl_attr_get_payload(tb[IFLA_ADDRESS])); + } + if (tb[IFLA_BROADCAST]) { + encode_kv_macaddr(nb, "mac_broadcast", mnl_attr_get_payload(tb[IFLA_BROADCAST])); + } + if (tb[IFLA_LINK]) { + encode_kv_ulong(nb, "link", mnl_attr_get_u32(tb[IFLA_LINK])); + } + if (tb[IFLA_OPERSTATE]) { + encode_kv_operstate(nb, mnl_attr_get_u32(tb[IFLA_OPERSTATE])); + } + if (tb[IFLA_STATS]) { + encode_kv_stats(nb, "stats", tb[IFLA_STATS]); + } + + if (tb[IFLA_IFNAME]) { + struct netif_link_settings ls = {0, }; + + int ret = 0; + + if((ret = ethtool_gset_ioctl(nb, mnl_attr_get_str(tb[IFLA_IFNAME]), &ls)) == 0) { + encode_kv_link_settings(nb, "link_settings", &ls); + } else { + error("[%s %d %s]: gset_ioctl returned %d!\r\n", __FILE__, __LINE__, __func__, ret); - return MNL_CB_OK; + encode_kv_link_settings(nb, "link_settings", NULL); + } + } else { + warn("[%s %d %s]: tb[IFLA_IFNAME] not present not encoding link settings!", __FILE__, __LINE__, __func__); + + encode_kv_link_settings(nb, "link_settings", NULL); + } + + return MNL_CB_OK; } static void nl_uevent_process(struct netif *nb) @@ -344,9 +446,7 @@ static void nl_uevent_process(struct netif *nb) // Check that we have the required fields that this is a // "net" subsystem event. If yes, send the notification. if (ifname && subsystem && ifindex && strcmp(subsystem, "net") == 0) { - nb->resp_index = sizeof(uint16_t); // Skip over payload size - nb->resp[nb->resp_index++] = 'n'; - ei_encode_version(nb->resp, &nb->resp_index); + start_notification(nb); ei_encode_tuple_header(nb->resp, &nb->resp_index, 2); @@ -374,23 +474,26 @@ static void nl_uevent_process(struct netif *nb) static void handle_notification(struct netif *nb, int bytecount) { + debug("[%s %d %s]: bytecount = %d\r\n", __FILE__, __LINE__, __func__, bytecount); + // Create the notification - nb->resp_index = sizeof(uint16_t); // Skip over payload size - nb->resp[nb->resp_index++] = 'n'; - ei_encode_version(nb->resp, &nb->resp_index); + start_notification(nb); ei_encode_tuple_header(nb->resp, &nb->resp_index, 2); // Currently, the only notifications are interface changes. ei_encode_atom(nb->resp, &nb->resp_index, "ifchanged"); + if (mnl_cb_run(nb->nlbuf, bytecount, 0, 0, netif_build_ifinfo, nb) <= 0) err(EXIT_FAILURE, "mnl_cb_run"); - erlcmd_send(nb->resp, nb->resp_index); + send_response(nb); } static void handle_async_response(struct netif *nb, int bytecount) { + debug("[%s %d %s]: bytecount = %d\r\n", __FILE__, __LINE__, __func__, bytecount); + nb->response_callback(nb, bytecount); nb->response_callback = NULL; nb->response_error_callback = NULL; @@ -398,6 +501,8 @@ static void handle_async_response(struct netif *nb, int bytecount) static void handle_async_response_error(struct netif *nb, int err) { + debug("[%s %d %s]: err = %d\r\n", __FILE__, __LINE__, __func__, err); + nb->response_error_callback(nb, err); nb->response_callback = NULL; nb->response_error_callback = NULL; @@ -406,6 +511,9 @@ static void handle_async_response_error(struct netif *nb, int err) static void nl_route_process(struct netif *nb) { int bytecount = mnl_socket_recvfrom(nb->nl, nb->nlbuf, sizeof(nb->nlbuf)); + + debug("[%s %d %s]: bytecount = %d\r\n", __FILE__, __LINE__, __func__, bytecount); + if (bytecount <= 0) err(EXIT_FAILURE, "mnl_socket_recvfrom"); @@ -446,6 +554,8 @@ static void netif_handle_interfaces(struct netif *nb) static void netif_handle_status_callback(struct netif *nb, int bytecount) { + debug("[%s %d %s]: bytecount = %d\r\n", __FILE__, __LINE__, __func__, bytecount); + start_response(nb); int original_resp_index = nb->resp_index; @@ -453,7 +563,7 @@ static void netif_handle_status_callback(struct netif *nb, int bytecount) ei_encode_tuple_header(nb->resp, &nb->resp_index, 2); ei_encode_atom(nb->resp, &nb->resp_index, "ok"); if (mnl_cb_run(nb->nlbuf, bytecount, nb->response_seq, nb->response_portid, netif_build_ifinfo, nb) < 0) { - debug("error from or mnl_cb_run?"); + debugf("error from or mnl_cb_run?"); nb->resp_index = original_resp_index; erlcmd_encode_errno_error(nb->resp, &nb->resp_index, errno); } @@ -463,6 +573,8 @@ static void netif_handle_status_callback(struct netif *nb, int bytecount) static void netif_handle_status_error_callback(struct netif *nb, int err) { + debug("[%s %d %s]: err = %d\r\n", __FILE__, __LINE__, __func__, err); + start_response(nb); erlcmd_encode_errno_error(nb->resp, &nb->resp_index, err); send_response(nb); @@ -507,7 +619,7 @@ static void netif_set_ifflags(struct netif *nb, start_response(nb); - strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); if (ioctl(nb->inet_fd, SIOCGIFFLAGS, &ifr) < 0) { debug("SIOCGIFFLAGS error: %s", strerror(errno)); erlcmd_encode_errno_error(nb->resp, &nb->resp_index, errno); @@ -565,6 +677,25 @@ static int check_default_gateway(const struct nlmsghdr *nlh, void *data) return MNL_CB_OK; } +static int check_default_gateway6(const struct nlmsghdr *nlh, void *data) +{ + struct nlattr *tb[RTA_MAX + 1]; + memset(tb, 0, sizeof(tb)); + struct rtmsg *rm = mnl_nlmsg_get_payload(nlh); + mnl_attr_parse(nlh, sizeof(*rm), collect_route_attrs, tb); + + struct fdg_data *fdg = (struct fdg_data *) data; + if (rm->rtm_scope == 0 && + tb[RTA_OIF] && + (int) mnl_attr_get_u32(tb[RTA_OIF]) == fdg->oif && + tb[RTA_GATEWAY]) { + // Found it. + inet_ntop(AF_INET6, mnl_attr_get_payload(tb[RTA_GATEWAY]), fdg->result, INET6_ADDRSTRLEN); + } + + return MNL_CB_OK; +} + static void find_default_gateway(struct netif *nb, int oif, char *result) @@ -602,6 +733,41 @@ static void find_default_gateway(struct netif *nb, err(EXIT_FAILURE, "mnl_socket_recvfrom"); } +static void find_default_gateway6(struct netif *nb, + int oif, + char *result) +{ + struct nlmsghdr *nlh = mnl_nlmsg_put_header(nb->nlbuf); + struct rtmsg *rtm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct rtmsg)); + unsigned int portid = 0; + unsigned int seq = 0; + ssize_t ret = 0; + struct fdg_data fdg = { .oif = oif, .result = result }; + + nlh->nlmsg_type = RTM_GETROUTE; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + nlh->nlmsg_seq = seq = nb->seq++; + + rtm->rtm_family = AF_INET6; + + if (mnl_socket_sendto(nb->nl, nlh, nlh->nlmsg_len) < 0) + err(EXIT_FAILURE, "mnl_socket_send"); + + mnl_socket_get_portid(nb->nl); + + fdg.result[0] = '\0'; + + ret = mnl_socket_recvfrom(nb->nl, nb->nlbuf, sizeof(nb->nlbuf)); + while (ret > 0) { + ret = mnl_cb_run(nb->nlbuf, ret, seq, portid, check_default_gateway6, &fdg); + if (ret <= MNL_CB_STOP) + break; + ret = mnl_socket_recvfrom(nb->nl, nb->nlbuf, sizeof(nb->nlbuf)); + } + if (ret == -1) + err(EXIT_FAILURE, "mnl_socket_recvfrom"); +} + struct ip_setting_handler { const char *name; int (*prep)(const struct ip_setting_handler *handler, struct netif *nb, void **context); @@ -617,24 +783,155 @@ static int prep_mac_address_ioctl(const struct ip_setting_handler *handler, stru static int set_mac_address_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context); static int get_mac_address_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname); static int prep_ipaddr_ioctl(const struct ip_setting_handler *handler, struct netif *nb, void **context); +static int prep_ipaddr(const struct ip_setting_handler *handler, struct netif *nb, void **context); static int set_ipaddr_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context); +static int remove_ipaddr(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context); static int get_ipaddr_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname); +static int set_ipaddr6_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context); +static int get_ipaddr6(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname); +static int remove_ipaddr6_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context); +static int prep_ipaddr6_ioctl(const struct ip_setting_handler *handler, struct netif *nb, void **context); static int prep_default_gateway(const struct ip_setting_handler *handler, struct netif *nb, void **context); static int set_default_gateway(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context); static int get_default_gateway(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname); - -// These handlers are listed in the order that they should be invoked when -// configuring the interface. For example, "ipv4_gateway" is listed at the end -// so that it is set after the address and subnet_mask. If this is not done, -// setting the gateway may fail since Linux thinks that it is on the wrong subnet. +static int prep_default_gateway6(const struct ip_setting_handler *handler, struct netif *nb, void **context); +static int set_default_gateway6(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context); +static int get_default_gateway6(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname); +static int prep_ipv6_autoconf(const struct ip_setting_handler *handler, struct netif *nb, void **context); +static int set_ipv6_autoconf(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context); +static int get_ipv6_autoconf(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname); +static int prep_ipv6_accept_ra(const struct ip_setting_handler *handler, struct netif *nb, void **context); +static int set_ipv6_accept_ra(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context); +static int get_ipv6_accept_ra(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname); +static int ifname_to_index(struct netif *nb, const char *ifname); +static int add_default_gateway6(struct netif *nb, const char *ifname, const char *gateway_ip, const unsigned short flags); +static int remove_gateway6_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context); +static int remove_gateway6(struct netif *nb, const char *ifname, const struct in6_addr *gw_addr, const unsigned short flags); +static int prep_ipv6_disable(const struct ip_setting_handler *handler, struct netif *nb, void **context); +static int set_ipv6_disable(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context); +static int get_ipv6_disable(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname); +static int prep_ipv6_accept_ra_pinfo(const struct ip_setting_handler *handler, struct netif *nb, void **context); +static int set_ipv6_accept_ra_pinfo(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context); +static int get_ipv6_accept_ra_pinfo(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname); +static int prep_ipv6_forwarding(const struct ip_setting_handler *handler, struct netif *nb, void **context); +static int set_ipv6_forwarding(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context); +static int get_ipv6_forwarding(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname); + + +/* These handlers are listed in the order that they should be invoked when + * configuring the interface. For example, "ipv4_gateway" is listed at the end + * so that it is set after the address and subnet_mask. If this is not done, + * setting the gateway may fail since Linux thinks that it is on the wrong subnet. + */ static const struct ip_setting_handler handlers[] = { - { "ipv4_address", prep_ipaddr_ioctl, set_ipaddr_ioctl, get_ipaddr_ioctl, SIOCSIFADDR, SIOCGIFADDR }, - { "ipv4_subnet_mask", prep_ipaddr_ioctl, set_ipaddr_ioctl, get_ipaddr_ioctl, SIOCSIFNETMASK, SIOCGIFNETMASK }, - { "ipv4_broadcast", prep_ipaddr_ioctl, set_ipaddr_ioctl, get_ipaddr_ioctl, SIOCSIFBRDADDR, SIOCGIFBRDADDR }, - { "ipv4_gateway", prep_default_gateway, set_default_gateway, get_default_gateway, 0, 0 }, - { "mac_address", prep_mac_address_ioctl, set_mac_address_ioctl, get_mac_address_ioctl, SIOCSIFHWADDR, SIOCGIFHWADDR }, + { .name = "ipv4_address", + .prep = prep_ipaddr_ioctl, + .set = set_ipaddr_ioctl, + .get = get_ipaddr_ioctl, + .ioctl_set = SIOCSIFADDR, + .ioctl_get = SIOCGIFADDR, + }, + { .name = "-ipv4_address", + .prep = prep_ipaddr, + .set = remove_ipaddr, + .get = NULL, + .ioctl_set = 0, + .ioctl_get = 0, + }, + { .name = "ipv4_subnet_mask", + .prep = prep_ipaddr_ioctl, + .set = set_ipaddr_ioctl, + .get = get_ipaddr_ioctl, + .ioctl_set = SIOCSIFNETMASK, + .ioctl_get = SIOCGIFNETMASK, + }, + { .name = "ipv4_broadcast", + .prep = prep_ipaddr_ioctl, + .set = set_ipaddr_ioctl, + .get = get_ipaddr_ioctl, + .ioctl_set = SIOCSIFBRDADDR, + .ioctl_get = SIOCGIFBRDADDR, + }, + { .name = "ipv6_address", + .prep = prep_ipaddr6_ioctl, + .set = set_ipaddr6_ioctl, + .get = get_ipaddr6, + .ioctl_set = SIOCSIFADDR, + .ioctl_get = 0, + }, + { .name = "ipv4_gateway", + .prep = prep_default_gateway, + .set = set_default_gateway, + .get = get_default_gateway, + .ioctl_set = 0, + .ioctl_get = 0, + }, + { .name = "ipv6_gateway", + .prep = prep_default_gateway6, + .set = set_default_gateway6, + .get = get_default_gateway6, + .ioctl_set = 0, + .ioctl_get = 0, + }, + { .name = "mac_address", + .prep = prep_mac_address_ioctl, + .set = set_mac_address_ioctl, + .get = get_mac_address_ioctl, + .ioctl_set = SIOCSIFHWADDR, + .ioctl_get = SIOCGIFHWADDR, + }, + { .name = "ipv6_autoconf", + .prep = prep_ipv6_autoconf, + .set = set_ipv6_autoconf, + .get = get_ipv6_autoconf, + .ioctl_set = 0, + .ioctl_get = 0, + }, + { .name = "ipv6_disable", + .prep = prep_ipv6_disable, + .set = set_ipv6_disable, + .get = get_ipv6_disable, + .ioctl_set = 0, + .ioctl_get = 0, + }, + { .name = "ipv6_accept_ra", + .prep = prep_ipv6_accept_ra, + .set = set_ipv6_accept_ra, + .get = get_ipv6_accept_ra, + .ioctl_set = 0, + .ioctl_get = 0, + }, + { .name = "ipv6_accept_ra_pinfo", + .prep = prep_ipv6_accept_ra_pinfo, + .set = set_ipv6_accept_ra_pinfo, + .get = get_ipv6_accept_ra_pinfo, + .ioctl_set = 0, + .ioctl_get = 0, + }, + { .name = "ipv6_forwarding", + .prep = prep_ipv6_forwarding, + .set = set_ipv6_forwarding, + .get = get_ipv6_forwarding, + .ioctl_set = 0, + .ioctl_get = 0, + }, + { .name = "-ipv6_address", + .prep = prep_ipaddr6_ioctl, + .set = remove_ipaddr6_ioctl, + .get = NULL, + .ioctl_set = SIOCDIFADDR, + .ioctl_get = 0, + }, + { .name = "-ipv6_gateway", + .prep = prep_default_gateway6, + .set = remove_gateway6_ioctl, + .get = NULL, + .ioctl_set = SIOCDELRT, + .ioctl_get = 0, + }, + { .name = NULL, } /* Setting-up a guard */ }; -#define HANDLER_COUNT (sizeof(handlers) / sizeof(handlers[0])) +#define HANDLER_COUNT ((sizeof(handlers)-1) / sizeof(handlers[0])) /* -1 is for the guard at the end of the array */ static int prep_mac_address_ioctl(const struct ip_setting_handler *handler, struct netif *nb, void **context) { @@ -642,8 +939,9 @@ static int prep_mac_address_ioctl(const struct ip_setting_handler *handler, stru if (erlcmd_decode_string(nb->req, &nb->req_index, macaddr_str, sizeof(macaddr_str)) < 0) errx(EXIT_FAILURE, "mac address parameter required for '%s'", handler->name); - // Be forgiving and if the user specifies an empty IP address, just skip - // this request. + /* Be forgiving and if the user specifies an empty IP address, just skip + * this request. + */ if (macaddr_str[0] == '\0') *context = NULL; else @@ -652,231 +950,1191 @@ static int prep_mac_address_ioctl(const struct ip_setting_handler *handler, stru return 0; } +struct ipv6_procfs_ctx { + char token[10]; /* ProcFs atom string true/false/override */ +}; +#define member_size(type, member) sizeof( ((type *) 0)->member) -static int set_mac_address_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +static int ipv6_read_integer_from_file(const struct ip_setting_handler *handler, struct netif *nb, const char *fname, int *val) { - const char *macaddr_str = (const char *) context; + FILE *f = NULL; - struct ifreq ifr; - memset(&ifr, 0, sizeof(ifr)); - strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + (void) handler; - struct sockaddr_in *addr = (struct sockaddr_in *) &ifr.ifr_addr; - addr->sin_family = AF_UNIX; - unsigned char *mac = (unsigned char *) &ifr.ifr_hwaddr.sa_data; - if (string_to_macaddr(macaddr_str, mac) < 0) { - debug("Bad MAC address for '%s': %s", handler->name, macaddr_str); - nb->last_error = EINVAL; + if((f = fopen(fname, "r")) == NULL) { + debug("Unable to open file '%s' for '%s'", fname, handler->name); + nb->last_error = errno; return -1; } - if (ioctl(nb->inet_fd, handler->ioctl_set, &ifr) < 0) { - debug("ioctl(0x%04x) failed for setting '%s': %s", handler->ioctl_set, handler->name, strerror(errno)); - nb->last_error = errno; + if(fscanf(f, "%d", val) < 0) { + nb->last_error = EIO; + fclose(f); return -1; } + fclose(f); return 0; } -static int get_mac_address_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname) +static int ipv6_write_integer_to_file(const struct ip_setting_handler *handler, struct netif *nb, const char *fname, const int val) { - struct ifreq ifr; - memset(&ifr, 0, sizeof(ifr)); - strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + FILE *f = NULL; - if (ioctl(nb->inet_fd, handler->ioctl_get, &ifr) < 0) { - debug("ioctl(0x%04x) failed for getting '%s': %s", handler->ioctl_get, handler->name, strerror(errno)); + (void) handler; + + if ((f = fopen(fname, "w")) == NULL) { + error("Unable to open file '%s' for '%s'", fname, handler->name); nb->last_error = errno; return -1; } - struct sockaddr_in *addr = (struct sockaddr_in *) &ifr.ifr_addr; - if (addr->sin_family == AF_UNIX) { - encode_kv_macaddr(nb, handler->name, (unsigned char *) &ifr.ifr_hwaddr.sa_data); - } else { - debug("got unexpected sin_family %d for '%s'", addr->sin_family, handler->name); - nb->last_error = EINVAL; + if (fprintf(f, "%d", val) < 0) { + error("Unable to write to the file '%s' for '%s'", fname, handler->name); + nb->last_error = EIO; + fclose(f); return -1; } + + fclose(f); + return 0; } -static int prep_ipaddr_ioctl(const struct ip_setting_handler *handler, struct netif *nb, void **context) + +static int prep_ipv6_autoconf(const struct ip_setting_handler *handler, struct netif *nb, void **context) { - char ipaddr[INET_ADDRSTRLEN]; - if (erlcmd_decode_string(nb->req, &nb->req_index, ipaddr, INET_ADDRSTRLEN) < 0) - errx(EXIT_FAILURE, "ip address parameter required for '%s'", handler->name); + struct ipv6_procfs_ctx *ac_ctx = *context = malloc(sizeof(struct ipv6_procfs_ctx)); - // Be forgiving and if the user specifies an empty IP address, just skip - // this request. - if (ipaddr[0] == '\0') - *context = NULL; - else - *context = strdup(ipaddr); + if(*context == NULL) { + errx(EXIT_FAILURE, "Unable to allocate memory for '%s'", handler->name); + } + + if ( erlcmd_decode_atom(nb->req, &nb->req_index, &ac_ctx->token[0], member_size(struct ipv6_procfs_ctx, token) ) < 0) + errx(EXIT_FAILURE, "Autoconf value true/false required for '%s'", handler->name); return 0; } -static int set_ipaddr_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +static int prep_ipv6_forwarding(const struct ip_setting_handler *handler, struct netif *nb, void **context) { - const char *ipaddr = (const char *) context; + struct ipv6_procfs_ctx *ac_ctx = *context = malloc(sizeof(struct ipv6_procfs_ctx)); - struct ifreq ifr; - memset(&ifr, 0, sizeof(ifr)); - strncpy(ifr.ifr_name, ifname, IFNAMSIZ); - - struct sockaddr_in *addr = (struct sockaddr_in *) &ifr.ifr_addr; - addr->sin_family = AF_INET; - if (inet_pton(AF_INET, ipaddr, &addr->sin_addr) <= 0) { - debug("Bad IP address for '%s': %s", handler->name, ipaddr); - nb->last_error = EINVAL; - return -1; + if(*context == NULL) { + errx(EXIT_FAILURE, "Unable to allocate memory for '%s'", handler->name); } - if (ioctl(nb->inet_fd, handler->ioctl_set, &ifr) < 0) { - debug("ioctl(0x%04x) failed for setting '%s': %s", handler->ioctl_set, handler->name, strerror(errno)); - nb->last_error = errno; - return -1; - } + if ( erlcmd_decode_atom(nb->req, &nb->req_index, &ac_ctx->token[0], member_size(struct ipv6_procfs_ctx, token) ) < 0) + errx(EXIT_FAILURE, "Autoconf value true/false required for '%s'", handler->name); return 0; } -static int get_ipaddr_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname) +static int prep_ipv6_disable(const struct ip_setting_handler *handler, struct netif *nb, void **context) { - struct ifreq ifr; - memset(&ifr, 0, sizeof(ifr)); - strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + struct ipv6_procfs_ctx *ac_ctx = *context = malloc(sizeof(struct ipv6_procfs_ctx)); - if (ioctl(nb->inet_fd, handler->ioctl_get, &ifr) < 0) { - debug("ioctl(0x%04x) failed for getting '%s': %s. Skipping...", handler->ioctl_get, handler->name, strerror(errno)); - encode_kv_string(nb, handler->name, ""); - return 0; + if(*context == NULL) { + errx(EXIT_FAILURE, "Unable to allocate memory for '%s'", handler->name); } - struct sockaddr_in *addr = (struct sockaddr_in *) &ifr.ifr_addr; - if (addr->sin_family == AF_INET) { - char addrstr[INET_ADDRSTRLEN]; - if (!inet_ntop(addr->sin_family, &addr->sin_addr, addrstr, sizeof(addrstr))) { - debug("inet_ntop failed for '%s'? : %s", handler->name, strerror(errno)); - nb->last_error = errno; - return -1; - } - encode_kv_string(nb, handler->name, addrstr); - } else { - debug("got unexpected sin_family %d for '%s'", addr->sin_family, handler->name); - nb->last_error = EINVAL; - return -1; - } + if ( erlcmd_decode_atom(nb->req, &nb->req_index, &ac_ctx->token[0], member_size(struct ipv6_procfs_ctx, token) ) < 0) + errx(EXIT_FAILURE, "ipv6_disable value true/false required for '%s'", handler->name); + return 0; } -static int remove_all_gateways(struct netif *nb, const char *ifname) +static int prep_ipv6_accept_ra_pinfo(const struct ip_setting_handler *handler, struct netif *nb, void **context) { - struct rtentry route; - memset(&route, 0, sizeof(route)); - - struct sockaddr_in *addr = (struct sockaddr_in *) &route.rt_gateway; - addr->sin_family = AF_INET; - addr->sin_addr.s_addr = INADDR_ANY; - - addr = (struct sockaddr_in*) &route.rt_dst; - addr->sin_family = AF_INET; - addr->sin_addr.s_addr = INADDR_ANY; + struct ipv6_procfs_ctx *ac_ctx = *context = malloc(sizeof(struct ipv6_procfs_ctx)); - addr = (struct sockaddr_in*) &route.rt_genmask; - addr->sin_family = AF_INET; - addr->sin_addr.s_addr = INADDR_ANY; + if(*context == NULL) { + errx(EXIT_FAILURE, "Unable to allocate memory for '%s'", handler->name); + } - route.rt_dev = (char *) ifname; - route.rt_flags = RTF_GATEWAY; - route.rt_metric = 0; + if ( erlcmd_decode_atom(nb->req, &nb->req_index, &ac_ctx->token[0], member_size(struct ipv6_procfs_ctx, token) ) < 0) + errx(EXIT_FAILURE, "Autoconf value true/false required for '%s'", handler->name); - // There may be more than one gateway. Remove all - // of them. - for (;;) { - int rc = ioctl(nb->inet_fd, SIOCDELRT, &route); - if (rc < 0) { - if (errno == ESRCH) { - return 0; - } else { - nb->last_error = errno; - return -1; - } - } - } + return 0; } -static int add_default_gateway(struct netif *nb, const char *ifname, const char *gateway_ip) +static int prep_ipv6_accept_ra(const struct ip_setting_handler *handler, struct netif *nb, void **context) { - struct rtentry route; - memset(&route, 0, sizeof(route)); + struct ipv6_procfs_ctx *ac_ctx = *context = malloc(sizeof(struct ipv6_procfs_ctx)); - struct sockaddr_in *addr = (struct sockaddr_in *)&route.rt_gateway; - memset(addr, 0, sizeof(struct sockaddr_in)); - addr->sin_family = AF_INET; - if (inet_pton(AF_INET, gateway_ip, &addr->sin_addr) <= 0) { - debug("Bad IP address for the default gateway: %s", gateway_ip); - nb->last_error = EINVAL; - return -1; + if(*context == NULL) { + errx(EXIT_FAILURE, "Unable to allocate memory for '%s'", handler->name); } - addr = (struct sockaddr_in*) &route.rt_dst; - memset(addr, 0, sizeof(struct sockaddr_in)); - addr->sin_family = AF_INET; - addr->sin_addr.s_addr = INADDR_ANY; - - addr = (struct sockaddr_in*) &route.rt_genmask; - memset(addr, 0, sizeof(struct sockaddr_in)); - addr->sin_family = AF_INET; - addr->sin_addr.s_addr = INADDR_ANY; + if ( erlcmd_decode_atom(nb->req, &nb->req_index, &ac_ctx->token[0], member_size(struct ipv6_procfs_ctx, token) ) < 0) + errx(EXIT_FAILURE, "Autoconf value true/false required for '%s'", handler->name); - route.rt_dev = (char *) ifname; - route.rt_flags = RTF_UP | RTF_GATEWAY; - route.rt_metric = 0; + return 0; +} - int rc = ioctl(nb->inet_fd, SIOCADDRT, &route); - if (rc < 0 && errno != EEXIST) { - nb->last_error = errno; +static int ipv6_bool_atom_to_integer(const struct ip_setting_handler *handler, struct netif *nb, const char *str, int *val) { + if (strcmp(str, "true") == 0) { + *val = 1; + } + else if (strcmp(str, "false") == 0) { + *val = 0; + } + else { + debug("Unsupported value of '%s' for '%s'", str, handler->name); return -1; } + return 0; } -static int prep_default_gateway(const struct ip_setting_handler *handler, struct netif *nb, void **context) -{ - char gateway[INET_ADDRSTRLEN]; - if (erlcmd_decode_string(nb->req, &nb->req_index, gateway, INET_ADDRSTRLEN) < 0) - errx(EXIT_FAILURE, "ip address parameter required for '%s'", handler->name); +static int ipv6_tri_state_atom_to_integer(const struct ip_setting_handler *handler, struct netif *nb, const char *str, int *val) { + if (strcmp(str, "true") == 0) { + *val = 1; + } + else if (strcmp(str, "false") == 0) { + *val = 0; + } + else if (strcmp(str, "override") == 0) { + *val = 2; + } + else { + debug("Unsupported value of '%s' for '%s'", str, handler->name); + return -1; + } - *context = strdup(gateway); return 0; } -static int set_default_gateway(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +static const char *ipv6_tri_state_integer_to_atom(const int val) { + static const char *atoms[] = { + "false", /* 0 */ + "true", /* 1 */ + "override", /* 2 */ + "" /* 3 */ + }; + + switch(val) { + case 0: + case 1: + case 2: + return atoms[val]; + default: + break; + }; + + return atoms[3]; +} + +static int ipv6_create_procfs_file_name(const struct ip_setting_handler *handler, struct netif *nb, char *dest, const size_t max_len, const char *ifname, const char *sys_fname) { (void) handler; - const char *gateway = context; - // Before one can be set, any configured gateways need to be removed. - if (remove_all_gateways(nb, ifname) < 0) + if ((unsigned int) snprintf(dest, max_len, "/proc/sys/net/ipv6/conf/%s/%s", ifname, sys_fname) >= max_len) { + debug("The file name truncated! Setting not performed for '%s'", handler->name); + nb->last_error = ENOMEM; return -1; + } - // If no gateway was specified, then we're done. - if (*gateway == '\0') - return 0; - - return add_default_gateway(nb, ifname, gateway); + return 0; } -static int ifname_to_index(struct netif *nb, const char *ifname) +/* The proc file's path is in the format: /proc/sys/net/ipv6/conf //accept_ra - hence we need + * IFNAMSIZ + 34 bytes for the remainder '/proc/sys/net/ipv6/conf//accept_ra' +1 for '\0' string termination */ +#define NETIF_IPV6_PROC_FILENAME_MAXLEN (IFNAMSIZ+60+1) + +static int set_ipv6_autoconf(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) { - struct ifreq ifr; - memset(&ifr, 0, sizeof(ifr)); - strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + struct ipv6_procfs_ctx *ac_ctx = (struct ipv6_procfs_ctx *) context; + char fname[NETIF_IPV6_PROC_FILENAME_MAXLEN] = {'\0', }; + int autoconf = 0; - if (ioctl(nb->inet_fd, SIOCGIFINDEX, &ifr) < 0) { + if(ipv6_create_procfs_file_name(handler, nb, fname, sizeof(fname), ifname, "autoconf") < 0) { + return -1; + } + + if (ipv6_bool_atom_to_integer(handler, nb, ac_ctx->token, &autoconf) < 0) { + nb->last_error = EOPNOTSUPP; + return -1; + } + + if(ipv6_write_integer_to_file(handler, nb, fname, autoconf) < 0){ + return -1; + } + + return 0; +} + +static int set_ipv6_forwarding(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +{ + struct ipv6_procfs_ctx *ac_ctx = (struct ipv6_procfs_ctx *) context; + char fname[NETIF_IPV6_PROC_FILENAME_MAXLEN] = {'\0', }; + int forwarding = 0; + + if(ipv6_create_procfs_file_name(handler, nb, fname, sizeof(fname), ifname, "forwarding") < 0) { + return -1; + } + + if (ipv6_bool_atom_to_integer(handler, nb, ac_ctx->token, &forwarding) < 0) { + nb->last_error = EOPNOTSUPP; + return -1; + } + + if(ipv6_write_integer_to_file(handler, nb, fname, forwarding) < 0){ + return -1; + } + + return 0; +} + +static int set_ipv6_disable(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +{ + struct ipv6_procfs_ctx *ac_ctx = (struct ipv6_procfs_ctx *) context; + char fname[NETIF_IPV6_PROC_FILENAME_MAXLEN] = {'\0', }; + int disable = 0; + + if(ipv6_create_procfs_file_name(handler, nb, fname, sizeof(fname), ifname, "disable_ipv6") < 0) { + return -1; + } + + if (ipv6_bool_atom_to_integer(handler, nb, ac_ctx->token, &disable) < 0) { + nb->last_error = EOPNOTSUPP; + return -1; + } + + if(ipv6_write_integer_to_file(handler, nb, fname, disable) < 0){ + return -1; + } + + return 0; +} + +static int set_ipv6_accept_ra_pinfo(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +{ + struct ipv6_procfs_ctx *ac_ctx = (struct ipv6_procfs_ctx *) context; + char fname[NETIF_IPV6_PROC_FILENAME_MAXLEN] = {'\0', }; + int accept_ra_pinfo = 0; + + if(ipv6_create_procfs_file_name(handler, nb, fname, sizeof(fname), ifname, "accept_ra_pinfo") < 0) { + return -1; + } + + if (ipv6_bool_atom_to_integer(handler, nb, ac_ctx->token, &accept_ra_pinfo) < 0) { + nb->last_error = EOPNOTSUPP; + return -1; + } + + if(ipv6_write_integer_to_file(handler, nb, fname, accept_ra_pinfo) < 0){ + return -1; + } + + return 0; +} + +static int get_ipv6_autoconf(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname) +{ + /* The proc file's path is in the format: /proc/sys/net/ipv6/conf //accept_ra - hence we need + * IFNAMSIZ + 34 bytes for the remainder '/proc/sys/net/ipv6/conf//accept_ra' +1 for '\0' string termination */ + char fname[NETIF_IPV6_PROC_FILENAME_MAXLEN] = {'\0', }; + int autoconf = 0; + + if(ipv6_create_procfs_file_name(handler, nb, &fname[0], sizeof(fname), ifname, "autoconf") < 0) { + debug("[%s %d] generated fname = '%s'\r\n", __FILE__, __LINE__, fname); + return -1; + } + + if(ipv6_read_integer_from_file(handler, nb, fname, &autoconf) < 0) { + return -1; + } + + encode_kv_bool(nb, handler->name, autoconf); + + return 0; +} + +static int get_ipv6_forwarding(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname) +{ + /* The proc file's path is in the format: /proc/sys/net/ipv6/conf //accept_ra - hence we need + * IFNAMSIZ + 34 bytes for the remainder '/proc/sys/net/ipv6/conf//accept_ra' +1 for '\0' string termination */ + char fname[NETIF_IPV6_PROC_FILENAME_MAXLEN] = {'\0', }; + int forwarding = 0; + + if(ipv6_create_procfs_file_name(handler, nb, &fname[0], sizeof(fname), ifname, "forwarding") < 0) { + debug("[%s %d] generated fname = '%s'\r\n", __FILE__, __LINE__, fname); + return -1; + } + + if(ipv6_read_integer_from_file(handler, nb, fname, &forwarding) < 0) { + return -1; + } + + encode_kv_bool(nb, handler->name, forwarding); + + return 0; +} + +static int get_ipv6_disable(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname) +{ + /* The proc file's path is in the format: /proc/sys/net/ipv6/conf //accept_ra - hence we need + * IFNAMSIZ + 34 bytes for the remainder '/proc/sys/net/ipv6/conf//accept_ra' +1 for '\0' string termination */ + char fname[NETIF_IPV6_PROC_FILENAME_MAXLEN] = {'\0', }; + int disable = 0; + + if(ipv6_create_procfs_file_name(handler, nb, &fname[0], sizeof(fname), ifname, "disable_ipv6") < 0) { + debug("[%s %d] generated fname = '%s'\r\n", __FILE__, __LINE__, fname); + return -1; + } + + if(ipv6_read_integer_from_file(handler, nb, fname, &disable) < 0) { + return -1; + } + + encode_kv_bool(nb, handler->name, disable); + + return 0; +} + +static int get_ipv6_accept_ra_pinfo(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname) +{ + /* The proc file's path is in the format: /proc/sys/net/ipv6/conf //accept_ra - hence we need + * IFNAMSIZ + 34 bytes for the remainder '/proc/sys/net/ipv6/conf//accept_ra' +1 for '\0' string termination */ + char fname[NETIF_IPV6_PROC_FILENAME_MAXLEN] = {'\0', }; + int accept_ra_pinfo = 0; + + if(ipv6_create_procfs_file_name(handler, nb, &fname[0], sizeof(fname), ifname, "accept_ra_pinfo") < 0) { + debug("[%s %d] generated fname = '%s'\r\n", __FILE__, __LINE__, fname); + return -1; + } + + if(ipv6_read_integer_from_file(handler, nb, fname, &accept_ra_pinfo) < 0) { + return -1; + } + + encode_kv_bool(nb, handler->name, accept_ra_pinfo); + + return 0; +} + +static int set_ipv6_accept_ra(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +{ + struct ipv6_procfs_ctx *ac_ctx = (struct ipv6_procfs_ctx *) context; + char fname[NETIF_IPV6_PROC_FILENAME_MAXLEN] = {'\0', }; + int accept_ra = 0; + + if(ipv6_create_procfs_file_name(handler, nb, fname, sizeof(fname), ifname, "accept_ra") < 0) { + return -1; + } + + if (ipv6_tri_state_atom_to_integer(handler, nb, ac_ctx->token, &accept_ra) < 0) { + nb->last_error = EOPNOTSUPP; + return -1; + } + + if(ipv6_write_integer_to_file(handler, nb, fname, accept_ra) < 0){ + return -1; + } + + return 0; +} + +static int get_ipv6_accept_ra(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname) +{ + /* The proc file's path is in the format: /proc/sys/net/ipv6/conf //accept_ra - hence we need + * IFNAMSIZ + 34 bytes for the remainder '/proc/sys/net/ipv6/conf//accept_ra' +1 for '\0' string termination */ + char fname[NETIF_IPV6_PROC_FILENAME_MAXLEN] = {'\0', }; + int accept_ra = 0; + + if(ipv6_create_procfs_file_name(handler, nb, &fname[0], sizeof(fname), ifname, "accept_ra") < 0) { + return -1; + } + + if(ipv6_read_integer_from_file(handler, nb, fname, &accept_ra) < 0) { + return -1; + } + + encode_kv_atom(nb, handler->name, ipv6_tri_state_integer_to_atom(accept_ra) ); + + return 0; +} + + +static int set_mac_address_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +{ + const char *macaddr_str = (const char *) context; + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); + + struct sockaddr_in *addr = (struct sockaddr_in *) &ifr.ifr_addr; + addr->sin_family = AF_UNIX; + unsigned char *mac = (unsigned char *) &ifr.ifr_hwaddr.sa_data; + if (string_to_macaddr(macaddr_str, mac) < 0) { + debug("Bad MAC address for '%s': %s", handler->name, macaddr_str); + nb->last_error = EINVAL; + return -1; + } + + if (ioctl(nb->inet_fd, handler->ioctl_set, &ifr) < 0) { + debug("ioctl(0x%04x) failed for setting '%s': %s", handler->ioctl_set, handler->name, strerror(errno)); + nb->last_error = errno; + return -1; + } + + return 0; +} + +static int get_mac_address_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname) +{ + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); + + if (ioctl(nb->inet_fd, handler->ioctl_get, &ifr) < 0) { + debug("ioctl(0x%04x) failed for getting '%s': %s", handler->ioctl_get, handler->name, strerror(errno)); + nb->last_error = errno; + return -1; + } + + struct sockaddr_in *addr = (struct sockaddr_in *) &ifr.ifr_addr; + if (addr->sin_family == AF_UNIX) { + encode_kv_macaddr(nb, handler->name, (unsigned char *) &ifr.ifr_hwaddr.sa_data); + } else { + debug("Got unexpected sin_family %d for '%s'", addr->sin_family, handler->name); + nb->last_error = EINVAL; + return -1; + } + return 0; +} + +static int prep_ipaddr_ioctl(const struct ip_setting_handler *handler, struct netif *nb, void **context) +{ + char ipaddr[INET_ADDRSTRLEN] = {'\0', }; + if (erlcmd_decode_string(nb->req, &nb->req_index, ipaddr, INET_ADDRSTRLEN) < 0) + errx(EXIT_FAILURE, "ip address parameter required for '%s'", handler->name); + + /* Be forgiving and if the user specifies an empty IP address, just skip + * this request. + */ + if (ipaddr[0] == '\0') + *context = NULL; + else + *context = strdup(ipaddr); + + return 0; +} + +static int prep_ipaddr(const struct ip_setting_handler *handler, struct netif *nb, void **context) +{ + #define PREFIX_LEN (3) /* ':' + 2 bytes i.e. 1.2.3.4:32 */ + char ipaddr[INET_ADDRSTRLEN+PREFIX_LEN] = {'\0', }; + if (erlcmd_decode_string(nb->req, &nb->req_index, ipaddr, INET_ADDRSTRLEN+PREFIX_LEN) < 0) + errx(EXIT_FAILURE, "ip address parameter required for '%s'", handler->name); + + + /* Be forgiving and if the user specifies an empty IP address, just skip + * this request. + */ + if (ipaddr[0] == '\0') + *context = NULL; + else + *context = strdup(ipaddr); + + return 0; +} + + +#define access_setting_handler(ptr) ((const struct ip_setting_handler *) ptr) +static int prep_ipaddr6_ioctl(const struct ip_setting_handler *handler, struct netif *nb, void **context) +{ + struct in6_ifreq *ifr6 = malloc(sizeof(struct in6_ifreq)); + char *prefix_ptr = (void *) NULL; + char ipaddr[INET6_ADDRSTRLEN] = {'\0', }; + + if(ifr6 == NULL) { + debug("Unable to allocate memory for '%s'", handler->name); + nb->last_error = ENOMEM; + return -1; + } + + *context = (void *) ifr6; + + memset(ifr6, 0, sizeof(*ifr6)); + + if (erlcmd_decode_string(nb->req, &nb->req_index, ipaddr, INET6_ADDRSTRLEN) < 0) + errx(EXIT_FAILURE, "ip address parameter required for '%s'", handler->name); + + /* Let's check whether prefix was provided as part of the address */ + prefix_ptr = strchr(ipaddr, (int) '/'); + + if(prefix_ptr != NULL) { + char *end_ptr = NULL; + long int retval = 0; + + /* Let's terminate the preceeding IPv6 address so we would be able to parse it later on */ + *prefix_ptr = '\0'; + + /* Parse the prefix */ + retval = strtol(&prefix_ptr[1], &end_ptr, 0); + + /* There were no digits to parse */ + if(&prefix_ptr[1] == end_ptr) { + nb->last_error = EINVAL; + return -1; + } + + /* Of course prefix cannot be a negative number and longer than the IPv6 address itself */ + if((retval < 0) || (retval > 128)) { + nb->last_error = EINVAL; + return -1; + } + + ifr6->ifr6_prefixlen = (int) retval; + } + + if (inet_pton(AF_INET6, ipaddr, &ifr6->ifr6_addr) <= 0) { + debug("Bad IP address for '%s': %s", handler->name, ipaddr); + nb->last_error = EINVAL; + return -1; + } + + /* We are restoring the original '/' caracter we temporarily replaced with '\0' */ + *prefix_ptr = '/'; + + return 0; +} + +static int set_ipaddr_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +{ + const char *ipaddr = (const char *) context; + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); + + struct sockaddr_in *addr = (struct sockaddr_in *) &ifr.ifr_addr; + + info("[%s %d %s]: address = '%s'\r\n", __FILE__, __LINE__, __func__, ipaddr); + + addr->sin_family = AF_INET; + + if (inet_pton(AF_INET, ipaddr, &addr->sin_addr) <= 0) { + error("Bad IP address for '%s': %s", handler->name, ipaddr); + nb->last_error = EINVAL; + return -1; + } + + if (ioctl(nb->inet_fd, handler->ioctl_set, &ifr) < 0) { + error("ioctl(0x%04x) failed for setting '%s': %s", handler->ioctl_set, handler->name, strerror(errno)); + nb->last_error = errno; + return -1; + } + + return 0; +} + +static int remove_ipaddr(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +{ + const char *ipaddr = (const char *) context; + char *prefix = (char *) strchr(context, ':'); + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); + + struct sockaddr_in *addr = (struct sockaddr_in *) &ifr.ifr_addr; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(nb->nlbuf); + struct ifaddrmsg *ipm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct ifaddrmsg));; + + int seq = nb->seq++; + int ret = 0; + + nlh->nlmsg_type = RTM_DELADDR; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_seq = seq; + + if(prefix != NULL) { + /* Prefix length attached */ + *prefix = '\0'; + prefix++; + ipm->ifa_prefixlen = atoi(prefix); + debug("[%s %d]: ipm->ifa_prefixlen = %d\r\n", __FILE__, __LINE__, ipm->ifa_prefixlen); + } + + ipm->ifa_family = AF_INET; + ipm->ifa_flags = 0; + ipm->ifa_scope = 0; + + debug("[%s %d]: remove_ipaddr for '%s'\r\n", __FILE__, __LINE__, ifname); + + addr->sin_family = AF_INET; + if (inet_pton(AF_INET, ipaddr, &addr->sin_addr) <= 0) { + debug("Bad IP address for '%s': %s", handler->name, ipaddr); + nb->last_error = EINVAL; + return -1; + } + + mnl_attr_put_u32(nlh, IFA_ADDRESS, addr->sin_addr.s_addr); + + if (ifname) { + ipm->ifa_index = ifname_to_index(nb, ifname); + + if (!ipm->ifa_index) { + debug("[%s %d]: No such device: '%s'\r\n", __FILE__, __LINE__, ifname); + nb->last_error = ENODEV; + return -1; + } + } + + if (mnl_socket_sendto(nb->nl, nlh, nlh->nlmsg_len) < 0) { + debug("[%s %d]: mnl_socket_sendto", __FILE__, __LINE__); + err(EXIT_FAILURE, "mnl_socket_sendto"); + } + + ret = mnl_socket_recvfrom(nb->nl, nb->nlbuf, sizeof(nb->nlbuf)); + if (ret < 0) { + debug("[%s %d]: mnl_socket_recvfrom", __FILE__, __LINE__); + err(EXIT_FAILURE, "mnl_socket_recvfrom"); + } + + { + unsigned int portid = mnl_socket_get_portid(nb->nl); + + debug("[%s %d %s]: mnl_cb_run(ret = %d; seq = %d; portid = %d\r\n", __FILE__, __LINE__, __func__, ret, seq, portid); + + ret = mnl_cb_run(nb->nlbuf, ret, seq, portid, NULL, NULL); + + if (ret < 0) { + debug("[%s %d %s]: mnl_cb_run ret = %d", __FILE__, __LINE__, __func__, ret); + return 0; + } + } + + debug("remove_ipaddr ok %s", ifname); + + return 0; +} + +static int get_ipaddr_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname) +{ + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); + + if (ioctl(nb->inet_fd, handler->ioctl_get, &ifr) < 0) { + debug("ioctl(0x%04x) failed for getting '%s': %s. Skipping...", handler->ioctl_get, handler->name, strerror(errno)); + encode_kv_string(nb, handler->name, ""); + return 0; + } + + struct sockaddr_in *addr = (struct sockaddr_in *) &ifr.ifr_addr; + if (addr->sin_family == AF_INET) { + char addrstr[INET_ADDRSTRLEN] = {'\0', }; + if (!inet_ntop(addr->sin_family, &addr->sin_addr, addrstr, sizeof(addrstr))) { + debug("inet_ntop failed for '%s'? : %s", handler->name, strerror(errno)); + nb->last_error = errno; + return -1; + } + encode_kv_string(nb, handler->name, addrstr); + } else { + debug("Got unexpected sin_family %d for '%s'", addr->sin_family, handler->name); + nb->last_error = EINVAL; + return -1; + } + return 0; +} + +static int get_if_index(struct netif *nb, const char *ifname, int * const if_index) +{ + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); + + if (ioctl(nb->inet_fd, SIOGIFINDEX, &ifr) < 0) { + nb->last_error = errno; + return -1; + } + + *if_index = ifr.ifr_ifindex; + return 0; +} + +#if defined(DEBUG) +static void byte_debug(char * caption, void *buf, int len) { + int i = 0; + fprintf(stderr, "======================== %s ========================\r\n", caption); + for(; i < len; i++) + fprintf(stderr, "%02x ", (int) ((unsigned char *) buf)[i]); +} +#else +# define byte_debug(caption, buf, len) +#endif /* DEBUG */ + +static int set_ipaddr6_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +{ + struct in6_ifreq *ifr6 = (struct in6_ifreq *) context; + + debug("[%s %d]: set_ipaddr6_ioctl\r\n", __FILE__, __LINE__); + + byte_debug("ifr6_addr", &ifr6->ifr6_addr, sizeof(ifr6->ifr6_addr)); + + if(get_if_index(nb, ifname, &ifr6->ifr6_ifindex) != 0) { + debug("Unable to obtain netif index '%s': %d", handler->name, ifr6->ifr6_ifindex); + nb->last_error = EINVAL; + return -1; + } + + debug("netif index '%s': %d", handler->name, ifr6->ifr6_ifindex); + + if (ioctl(nb->inet6_fd, handler->ioctl_set, ifr6) < 0) { + debug("ioctl(0x%04x) failed for setting '%s': %s", handler->ioctl_set, handler->name, strerror(errno)); + nb->last_error = errno; + return -1; + } + + return 0; +} + +static int remove_ipaddr6_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +{ + struct in6_ifreq *ifr6 = (struct in6_ifreq *) context; + + debug("[%s %d]: remove_ipaddr6_ioctl\r\n", __FILE__, __LINE__); + + byte_debug("ifr6_addr", &ifr6->ifr6_addr, sizeof(ifr6->ifr6_addr)); + + if(get_if_index(nb, ifname, &ifr6->ifr6_ifindex) != 0) { + debug("Unable to obtain netif index '%s': %d", handler->name, ifr6->ifr6_ifindex); + nb->last_error = EINVAL; + return -1; + } + + debug("netif index '%s': %d", handler->name, ifr6->ifr6_ifindex); + + if (ioctl(nb->inet6_fd, handler->ioctl_set, ifr6) < 0) { + debug("ioctl(0x%04x) failed for setting '%s': %s", handler->ioctl_set, handler->name, strerror(errno)); + nb->last_error = errno; + return -1; + } + + return 0; +} + +#define SCOPE_STR_MAX_LEN (16) + +static char * get_ip6_address_scope(char * restrict scope_str, const struct in6_addr *addr) { + if(IN6_IS_ADDR_LINKLOCAL(addr)) { + return strncpy(scope_str, "link-local", SCOPE_STR_MAX_LEN); + } else if(IN6_IS_ADDR_SITELOCAL(addr)) { + return strncpy(scope_str, "site-local", SCOPE_STR_MAX_LEN); + } else if(IN6_IS_ADDR_UNSPECIFIED(addr)) { + return strncpy(scope_str, "unspecified", SCOPE_STR_MAX_LEN); + } else if(IN6_IS_ADDR_LOOPBACK(addr)) { + return strncpy(scope_str, "loopback", SCOPE_STR_MAX_LEN); + } else if(IN6_IS_ADDR_V4MAPPED(addr)) { + return strncpy(scope_str, "v4-mapped", SCOPE_STR_MAX_LEN); + } else if(IN6_IS_ADDR_V4COMPAT(addr)) { + return strncpy(scope_str, "v4-compat", SCOPE_STR_MAX_LEN); + } else if(IN6_IS_ADDR_MC_NODELOCAL(addr)) { + return strncpy(scope_str, "node-local", SCOPE_STR_MAX_LEN); + } else if(IN6_IS_ADDR_MC_LINKLOCAL(addr)) { + return strncpy(scope_str, "link-local", SCOPE_STR_MAX_LEN); + } else if(IN6_IS_ADDR_MC_SITELOCAL(addr)) { + return strncpy(scope_str, "site-local", SCOPE_STR_MAX_LEN); + } else if(IN6_IS_ADDR_MC_ORGLOCAL(addr)) { + return strncpy(scope_str, "org-local", SCOPE_STR_MAX_LEN); + } else if(IN6_IS_ADDR_MC_GLOBAL(addr)) { + return strncpy(scope_str, "global", SCOPE_STR_MAX_LEN); + } else { + return strncpy(scope_str, "global", SCOPE_STR_MAX_LEN); + } + return scope_str; +} + +/* Encodes the erlang list of IPv6 addresses' strings in the form of [ a | [b | [c] ] bound to a network interface if the ifname name */ +static int get_ipaddr6(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname) +{ + FILE *f = fopen("/proc/net/if_inet6", "r"); + struct in6_addr ip6 = in6addr_any; + char parsed_ifname[IFNAMSIZ] = {'\0', }; + char addr_scope[SCOPE_STR_MAX_LEN] = {'\0', }; + unsigned int prefix = 0; + unsigned int scope = 0; + + if(f == NULL) { + debug("[%s %d]: Unable to open file for reading!!", __FILE__, __LINE__); + nb->last_error = errno; + return -1; + } + /* A sample entry for an interface fe80000000000000020c29fffe9c009e 02 40 20 80 eth0 */ + /* 00000000000000000000000000000001 01 80 10 80 lo + +------------------------------+ ++ ++ ++ ++ ++ + | | | | | | + 1 2 3 4 5 6 + + 1. IPv6 address displayed in 32 hexadecimal chars without colons as separator + 2. Netlink device number (interface index) in hexadecimal (see ”ip addr” , too) + 3. Prefix length in hexadecimal + 4. Scope value (see kernel source ” include/net/ipv6.h” and ”net/ipv6/addrconf.c” for more) + 5. Interface flags (see ”include/linux/rtnetlink.h” and ”net/ipv6/addrconf.c” for more) + 6. Device name + */ + ei_encode_atom(nb->resp, &nb->resp_index, handler->name); + while(19 == fscanf(f, "%2hhx%2hhx%2hhx%2hhx""%2hhx%2hhx%2hhx%2hhx""%2hhx%2hhx%2hhx%2hhx""%2hhx%2hhx%2hhx%2hhx""%*x %x %x %*x %s", + &ip6.s6_addr[0], + &ip6.s6_addr[1], + &ip6.s6_addr[2], + &ip6.s6_addr[3], + &ip6.s6_addr[4], + &ip6.s6_addr[5], + &ip6.s6_addr[6], + &ip6.s6_addr[7], + &ip6.s6_addr[8], + &ip6.s6_addr[9], + &ip6.s6_addr[10], + &ip6.s6_addr[11], + &ip6.s6_addr[12], + &ip6.s6_addr[13], + &ip6.s6_addr[14], + &ip6.s6_addr[15], + &prefix, &scope, &parsed_ifname[0])) { + char address[INET6_ADDRSTRLEN] = {'\0', }; + char prefix_str[MAX_PREFIX_LEN] = {'\0', }; + + if(strcmp(parsed_ifname, ifname) != 0) { + debug("[%s %d]: Parsed ifname '%s' neq ifname = '%s' skipping...", __FILE__, __LINE__, parsed_ifname, ifname); + continue; + } + + if(inet_ntop(AF_INET6, &ip6, address, sizeof(address)) == NULL) { + debug("[%s %d]: Invalid IP address skipping...", __FILE__, __LINE__); + continue; + } else { + /* Decode Scope */ + (void) get_ip6_address_scope(&addr_scope[0], &ip6); + debug("[%s %d]: Address scope = '%s'", __FILE__, __LINE__, addr_scope); + } + + snprintf(prefix_str, sizeof(prefix_str), "/%d", prefix); + + debug("[%s %d]: address = '%s'", __FILE__, __LINE__, address); + debug("[%s %d]: prefix = '%s'", __FILE__, __LINE__, prefix_str); + strcat(address, prefix_str); + debug("[%s %d]: Result address = '%s'", __FILE__, __LINE__, address); + /* Let's concat the prefix length in the IPv6 address string */ + ei_encode_list_header(nb->resp, &nb->resp_index, 1); + ei_encode_map_header(nb->resp, &nb->resp_index, 2); + ei_encode_atom(nb->resp, &nb->resp_index, "address"); + encode_string(nb->resp, &nb->resp_index, address); + ei_encode_atom(nb->resp, &nb->resp_index, "scope"); + encode_string(nb->resp, &nb->resp_index, addr_scope); + } /* while (19 == fscanf(...) */ + ei_encode_empty_list(nb->resp, &nb->resp_index); + + fclose(f); + + return 0; +} + +static int remove_all_gateways(struct netif *nb, const char *ifname) +{ + struct rtentry route; + memset(&route, 0, sizeof(route)); + + struct sockaddr_in *addr = (struct sockaddr_in *) &route.rt_gateway; + addr->sin_family = AF_INET; + addr->sin_addr.s_addr = INADDR_ANY; + + addr = (struct sockaddr_in*) &route.rt_dst; + addr->sin_family = AF_INET; + addr->sin_addr.s_addr = INADDR_ANY; + + addr = (struct sockaddr_in*) &route.rt_genmask; + addr->sin_family = AF_INET; + addr->sin_addr.s_addr = INADDR_ANY; + + route.rt_dev = (char *) ifname; + route.rt_flags = RTF_GATEWAY; + route.rt_metric = 0; + + // There may be more than one gateway. Remove all + // of them. + for (;;) { + int rc = ioctl(nb->inet_fd, SIOCDELRT, &route); + if (rc < 0) { + if (errno == ESRCH) { + return 0; + } else { + nb->last_error = errno; + return -1; + } + } + } +} + +static int remove_gateway6(struct netif *nb, const char *ifname, const struct in6_addr *gw_addr, const unsigned short flags) +{ + struct in6_rtmsg route = {0, }; + struct in6_addr *gw = (struct in6_addr *) &route.rtmsg_gateway; + struct in6_addr *dst = (struct in6_addr *) &route.rtmsg_dst; + int rc = 0; + + *gw = *gw_addr; + *dst = in6addr_any; + + route.rtmsg_dst_len = 0; + route.rtmsg_ifindex = ifname_to_index(nb, ifname); + route.rtmsg_flags = RTF_UP | RTF_GATEWAY | flags; /* if the RTF_GATEWAY flag is set the gateway ip must exactly mach the one in the fib table */ + route.rtmsg_metric = 256; + + rc = ioctl(nb->inet6_fd, SIOCDELRT, &route); + + debug("Removing GW returned rc = %d\r\n", rc); + + if (rc < 0) { + if (errno == ESRCH) { + return 0; + } else { + nb->last_error = errno; + debug("Removing GW returnt rc = %d; errno = %d '%s'\r\n", rc, errno, strerror(errno)); + return -1; + } + } + + return 0; +} + +static int remove_all_gateways6(struct netif *nb, const char *ifname) +{ + struct in6_rtmsg route = {0, }; + struct in6_addr *gw = (struct in6_addr *) &route.rtmsg_gateway; + struct in6_addr *dst = (struct in6_addr *) &route.rtmsg_dst; + + *gw = in6addr_any; + *dst = in6addr_any; + + route.rtmsg_dst_len = 0; + route.rtmsg_ifindex = ifname_to_index(nb, ifname); + route.rtmsg_flags = RTF_UP; /* if the RTF_GATEWAY flag is set the gateway ip must exactly mach the one in the fib table */ + route.rtmsg_metric = 0; + + /* There may be more than one gateway. Remove all of them. */ + for (;;) { + int rc = ioctl(nb->inet6_fd, SIOCDELRT, &route); + debug("Removing GW returnt rc = %d\r\n", rc); + if (rc < 0) { + if (errno == ESRCH) { + return 0; + } else { + nb->last_error = errno; + debug("Removing GW returnt rc = %d\r\n", rc); + return -1; + } + } + } +} + +static int add_default_gateway(struct netif *nb, const char *ifname, const char *gateway_ip) +{ + struct nlmsghdr *nlh = mnl_nlmsg_put_header(nb->nlbuf); + struct rtmsg *rtm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct rtmsg));; + + int seq = nb->seq++; + int ret = 0; + + memset(rtm, 0, sizeof(*rtm)); + + nlh->nlmsg_type = RTM_NEWROUTE; + + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK; + nlh->nlmsg_seq = seq; + + rtm->rtm_family = AF_INET; + rtm->rtm_table = RT_TABLE_MAIN; + rtm->rtm_scope = RT_SCOPE_UNIVERSE; + rtm->rtm_protocol = RTPROT_BOOT; + rtm->rtm_type = RTN_UNICAST; + rtm->rtm_flags = RTNH_F_ONLINK; + + { + struct sockaddr_in addr; + memset(&addr, 0, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + + if (inet_pton(AF_INET, gateway_ip, &addr.sin_addr) <= 0) { + error("Bad IP address for the default gateway: %s", gateway_ip); + nb->last_error = EINVAL; + return -1; + } + + mnl_attr_put_u32(nlh, RTA_GATEWAY, addr.sin_addr.s_addr); + } + + if (ifname) { + int idx = ifname_to_index(nb, ifname); + + if (!idx) { + debug("[%s %d]: No such device: '%s'\r\n", __FILE__, __LINE__, ifname); + nb->last_error = ENODEV; + return -1; + } + + mnl_attr_put_u32(nlh, RTA_OIF, idx); + } + + + if (mnl_socket_sendto(nb->nl, nlh, nlh->nlmsg_len) < 0) { + error("[%s %d %s]: mnl_socket_sendto", __FILE__, __LINE__, __func__); + err(EXIT_FAILURE, "mnl_socket_sendto"); + } + + ret = mnl_socket_recvfrom(nb->nl, nb->nlbuf, sizeof(nb->nlbuf)); + if (ret < 0) { + error("[%s %d %s]: mnl_socket_recvfrom", __FILE__, __LINE__, __func__); + + err(EXIT_FAILURE, "mnl_socket_recvfrom"); + } + + { + unsigned int portid = mnl_socket_get_portid(nb->nl); + ret = mnl_cb_run(nb->nlbuf, ret, seq, portid, NULL, NULL); + + if (ret < 0) { + error("[%s %d %s]: mnl_cb_run errno = %d '%s'", __FILE__, __LINE__, __func__, errno, strerror(errno)); + nb->last_error = errno; + return -1; + } + } + + debug("add_default_gateway ok %s", ifname); + return 0; +} + +static int prep_default_gateway(const struct ip_setting_handler *handler, struct netif *nb, void **context) +{ + char gateway[INET_ADDRSTRLEN]; + if (erlcmd_decode_string(nb->req, &nb->req_index, gateway, INET_ADDRSTRLEN) < 0) + errx(EXIT_FAILURE, "ip address parameter required for '%s'", handler->name); + + *context = strdup(gateway); + return 0; +} + +static int prep_default_gateway6(const struct ip_setting_handler *handler, struct netif *nb, void **context) +{ + char gateway[INET6_ADDRSTRLEN] = {'\0', }; + if (erlcmd_decode_string(nb->req, &nb->req_index, gateway, INET6_ADDRSTRLEN) < 0) + errx(EXIT_FAILURE, "ip address parameter required for '%s'", handler->name); + + *context = strdup(gateway); + return 0; +} + +static int set_default_gateway6(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +{ + (void) handler; + const char *gateway = context; + + /* If no gateway was specified, then we're done. */ + if (*gateway == '\0') + return 0; + + return add_default_gateway6(nb, ifname, gateway, 0); +} + +static int remove_gateway6_ioctl(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +{ + (void) handler; + const char *gateway_ip = context; + struct in6_addr gw = in6addr_any; + + /* If IPv6 gateway's address is an empty string "", then we remove all */ + if (*gateway_ip == '\0') + return remove_all_gateways6(nb, ifname); + + if (inet_pton(AF_INET6, gateway_ip, (void *) &gw) <= 0) { + error("Bad IP address for the default gateway v6: %s", gateway_ip); + nb->last_error = EINVAL; + return -1; + } + + /* If IPv6 gateway-for-removal's address is ANY i.e. :: all default gateways ar targetted for removal */ + if(memcmp(&gw, &in6addr_any, sizeof(struct in6_addr)) == 0) + return remove_all_gateways6(nb, ifname); + + return remove_gateway6(nb, ifname, &gw, 0); +} + +static int get_default_gateway6(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname) +{ + int oif = ifname_to_index(nb, ifname); + char gateway_ip[INET6_ADDRSTRLEN] = {'\0', }; + + if (oif < 0) + return -1; + + find_default_gateway6(nb, oif, gateway_ip); + debug("[%s %d]: gateway6_ip = '%s'\r\n", __FILE__, __LINE__, gateway_ip); + + /* If the gateway isn't found, then the empty string is what we want. */ + encode_kv_string(nb, handler->name, gateway_ip); + return 0; +} + +static int add_default_gateway6(struct netif *nb, const char *ifname, const char *gateway_ip, const unsigned short flags) +{ + struct in6_rtmsg route = {0, }; + struct in6_addr *dst = (struct in6_addr *) &route.rtmsg_dst; + struct in6_addr *gw = (struct in6_addr *) &route.rtmsg_gateway; + + *dst = in6addr_any; + + if (inet_pton(AF_INET6, gateway_ip, (void *) gw) <= 0) { + error("Bad IP address for the default gateway v6: %s", gateway_ip); + nb->last_error = EINVAL; + return -1; + } + + route.rtmsg_dst_len = 0; /* router does not have to have a prefix */ + route.rtmsg_flags = RTF_UP | RTF_GATEWAY | flags; + route.rtmsg_metric = 1; + route.rtmsg_ifindex = ifname_to_index(nb, ifname); + + int rc = ioctl(nb->inet6_fd, SIOCADDRT, &route); + if (rc < 0 && errno != EEXIST) { + error("IOCTL failed for the default gateway v6: %s; errno = %d : '%s'", gateway_ip, errno, strerror(errno)); + nb->last_error = errno; + return -1; + } + return 0; +} + +static int set_default_gateway(const struct ip_setting_handler *handler, struct netif *nb, const char *ifname, void *context) +{ + (void) handler; + const char *gateway = context; + + info("set_default_gateway %s; gateway = '%s'", ifname, gateway); + + /* Before one can be set, any configured gateways need to be removed. */ + if (remove_all_gateways(nb, ifname) < 0) { + error("remove_all_gateways failed for '%s' : %s", handler->name, gateway); + return -1; + } + + /* If no gateway was specified, then we're done. */ + if (*gateway == '\0') + return 0; + + return add_default_gateway(nb, ifname, gateway); +} + +static int ifname_to_index(struct netif *nb, const char *ifname) +{ + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); + + if (ioctl(nb->inet_fd, SIOCGIFINDEX, &ifr) < 0) { nb->last_error = errno; return -1; } @@ -897,93 +2155,135 @@ static int get_default_gateway(const struct ip_setting_handler *handler, struct return 0; } + +static size_t netif_count_rw_handlers() +{ + const struct ip_setting_handler *handler; + size_t count = 0; + + for(handler = &handlers[0]; handler->name != NULL; handler++) { + if(handler->get != NULL) { + count++; + } + } + + return count; +} + static void netif_handle_get(struct netif *nb, const char *ifname) { - start_response(nb); + start_response(nb); + { int original_resp_index = nb->resp_index; + size_t i = 0; ei_encode_tuple_header(nb->resp, &nb->resp_index, 2); ei_encode_atom(nb->resp, &nb->resp_index, "ok"); - ei_encode_map_header(nb->resp, &nb->resp_index, HANDLER_COUNT); + ei_encode_map_header(nb->resp, &nb->resp_index, netif_count_rw_handlers()); nb->last_error = 0; - for (size_t i = 0; i < HANDLER_COUNT; i++) { - const struct ip_setting_handler *handler = &handlers[i]; - if (handler->get(handler, nb, ifname) < 0) - break; + + for (; i < HANDLER_COUNT; i++) { + const struct ip_setting_handler *handler = &handlers[i]; + + if(handler->get == NULL) /* There are handlers that are for write only operations and do not implement get function */ + continue; + if (handler->get(handler, nb, ifname) < 0) + break; } if (nb->last_error) { - nb->resp_index = original_resp_index; - erlcmd_encode_errno_error(nb->resp, &nb->resp_index, nb->last_error); + nb->resp_index = original_resp_index; + erlcmd_encode_errno_error(nb->resp, &nb->resp_index, nb->last_error); } + send_response(nb); + } } static const struct ip_setting_handler *find_handler(const char *name) { - for (size_t i = 0; i < HANDLER_COUNT; i++) { - const struct ip_setting_handler *handler = &handlers[i]; - if (strcmp(handler->name, name) == 0) - return handler; - } - return NULL; + for (size_t i = 0; i < HANDLER_COUNT; i++) { + const struct ip_setting_handler *handler = &handlers[i]; + if (strcmp(handler->name, name) == 0) + return handler; + } + return NULL; } static void netif_handle_set(struct netif *nb, const char *ifname) { - nb->last_error = 0; + void *handler_context[HANDLER_COUNT] = { NULL, }; + int arity = 0; - start_response(nb); + start_response(nb); - int arity; - if (ei_decode_map_header(nb->req, &nb->req_index, &arity) < 0) - errx(EXIT_FAILURE, "setting attributes requires a map"); - - void *handler_context[HANDLER_COUNT]; - memset(handler_context, 0, sizeof(handler_context)); - - // Parse all options - for (int i = 0; i < arity && nb->last_error == 0; i++) { - char name[32]; - if (erlcmd_decode_atom(nb->req, &nb->req_index, name, sizeof(name)) < 0) - errx(EXIT_FAILURE, "error in map encoding"); - - // Look up the option. If we don't know it, silently ignore it so that - // the caller can pass in maps that contain options for other code. - const struct ip_setting_handler *handler = find_handler(name); - if (handler) - handler->prep(handler, nb, &handler_context[handler - handlers]); - else - ei_skip_term(nb->req, &nb->req_index); - } + nb->last_error = 0; - // If no errors, then set everything - if (!nb->last_error) { - // Order is important: see note on handlers - for (size_t i = 0; i < HANDLER_COUNT; i++) { - if (handler_context[i]) { - handlers[i].set(&handlers[i], nb, ifname, handler_context[i]); - free(handler_context[i]); - } - } + if (ei_decode_map_header(nb->req, &nb->req_index, &arity) < 0) + errx(EXIT_FAILURE, "setting attributes requires a map"); + + // Parse all options + for (int i = 0; i < arity && nb->last_error == 0; i++) { + struct ip_setting_handler *handler = NULL; + char name[32] = {'\0', }; + + if (erlcmd_decode_atom(nb->req, &nb->req_index, name, sizeof(name)) < 0) + errx(EXIT_FAILURE, "error in map encoding"); + + // Look up the option. If we don't know it, silently ignore it so that + // the caller can pass in maps that contain options for other code. + handler = (struct ip_setting_handler *) find_handler(name); + + debug("%s %d]: handler for '%s' = %p\r\n", __FILE__, __LINE__, name, (void *) handler); + + if (handler != NULL) { + handler->prep(handler, nb, &handler_context[handler - handlers]); + } else { + debug("%s %d]: No known handler for '%s' = %p! Skipping term...\r\n", __FILE__, __LINE__, name, (void *) handler); + ei_skip_term(nb->req, &nb->req_index); + } + } + + // If no errors, then set everything + if (!nb->last_error) { + size_t i; + // Order is important: see note on handlers + for (i = 0; i < HANDLER_COUNT; i++) { + if (handler_context[i]) { + handlers[i].set(&handlers[i], nb, ifname, handler_context[i]); + } } + } + + /* Let's free all contextes that had been allocated above by the prep handlers */ + { + size_t i; + for (i = 0; i < HANDLER_COUNT; i++) { + if (handler_context[i]) { + free(handler_context[i]); + handler_context[i] = NULL; + } + } + } - // Encode and send the response - if (nb->last_error) - erlcmd_encode_errno_error(nb->resp, &nb->resp_index, nb->last_error); - else - erlcmd_encode_ok(nb->resp, &nb->resp_index); + debug("[%s %d]: last_error = %d\r\n", __FILE__, __LINE__, nb->last_error); - send_response(nb); + // Encode and send the response + if (nb->last_error) + erlcmd_encode_errno_error(nb->resp, &nb->resp_index, nb->last_error); + else + erlcmd_encode_ok(nb->resp, &nb->resp_index); + + send_response(nb); } static void netif_request_handler(const char *req, void *cookie) { struct netif *nb = (struct netif *) cookie; - char ifname[IFNAMSIZ]; + char ifname[IFNAMSIZ] = {'\0', }; // Commands are of the form {Command, Arguments}: // { atom(), term() } @@ -1002,7 +2302,7 @@ static void netif_request_handler(const char *req, void *cookie) errx(EXIT_FAILURE, "expecting command atom"); if (strcmp(cmd, "interfaces") == 0) { - debug("interfaces"); + debugf("interfaces"); netif_handle_interfaces(nb); } else if (strcmp(cmd, "status") == 0) { if (erlcmd_decode_string(nb->req, &nb->req_index, ifname, IFNAMSIZ) < 0) @@ -1022,8 +2322,10 @@ static void netif_request_handler(const char *req, void *cookie) } else if (strcmp(cmd, "setup") == 0) { if (ei_decode_tuple_header(nb->req, &nb->req_index, &arity) < 0 || arity != 2 || - erlcmd_decode_string(nb->req, &nb->req_index, ifname, IFNAMSIZ) < 0) + erlcmd_decode_string(nb->req, &nb->req_index, ifname, IFNAMSIZ) < 0) { + debugf("setup requires {ifname, parameters}"); errx(EXIT_FAILURE, "setup requires {ifname, parameters}"); + } debug("set: %s", ifname); netif_handle_set(nb, ifname); } else if (strcmp(cmd, "settings") == 0) {