1## This Source Code Form is subject to the terms of the Mozilla Public
2## License, v. 2.0. If a copy of the MPL was not distributed with this
3## file, You can obtain one at https://mozilla.org/MPL/2.0/.
4##
5## Copyright (c) 2007-2021 VMware, Inc. or its affiliates.  All rights reserved.
6
7defmodule RabbitMQ.CLI.Diagnostics.Commands.ResolveHostnameCommand do
8  @moduledoc """
9  Resolves a hostname to one or more addresses of a given IP address family (IPv4 ot IPv6).
10  This command is not meant to compete with `dig` but rather provide a way
11  to perform basic resolution tests that take Erlang's inetrc file into account.
12  """
13
14  import RabbitCommon.Records
15  alias RabbitMQ.CLI.Core.Networking
16  alias RabbitMQ.CLI.Core.ExitCodes
17
18  @behaviour RabbitMQ.CLI.CommandBehaviour
19
20  def scopes(), do: [:diagnostics]
21
22  def switches(), do: [address_family: :string, offline: :boolean]
23  def aliases(), do: [a: :address_family]
24
25  def merge_defaults(args, opts) do
26    {args, Map.merge(%{address_family: "IPv4", offline: false}, opts)}
27  end
28
29  def validate(args, _) when length(args) < 1, do: {:validation_failure, :not_enough_args}
30  def validate(args, _) when length(args) > 1, do: {:validation_failure, :too_many_args}
31  def validate([_], %{address_family: family}) do
32    case Networking.valid_address_family?(family) do
33      true  -> :ok
34      false -> {:validation_failure, {:bad_argument, "unsupported IP address family #{family}. Valid values are: ipv4, ipv6"}}
35    end
36  end
37  def validate([_], _), do: :ok
38
39  def run([hostname], %{address_family: family, offline: true}) do
40    :inet.gethostbyname(to_charlist(hostname), Networking.address_family(family))
41  end
42  def run([hostname], %{node: node_name, address_family: family, offline: false, timeout: timeout}) do
43    case :rabbit_misc.rpc_call(node_name, :inet, :gethostbyname,
44           [to_charlist(hostname), Networking.address_family(family)], timeout) do
45      {:error, _} = err -> err
46      {:error, _, _} = err -> err
47      {:ok, result} -> {:ok, result}
48      other -> other
49    end
50  end
51
52  def output({:error, :nxdomain}, %{node: node_name, formatter: "json"}) do
53    m = %{
54      "result"  => "error",
55      "node"    => node_name,
56      "message" => "Hostname does not resolve (resolution failed with an nxdomain)"
57    }
58    {:error, ExitCodes.exit_dataerr(), m}
59  end
60  def output({:error, :nxdomain}, _opts) do
61    {:error, ExitCodes.exit_dataerr(), "Hostname does not resolve (resolution failed with an nxdomain)"}
62  end
63  def output({:ok, result}, %{node: node_name, address_family: family, formatter: "json"}) do
64    hostname  = hostent(result, :h_name)
65    addresses = hostent(result, :h_addr_list)
66    {:ok, %{
67      "result"         => "ok",
68      "node"           => node_name,
69      "hostname"       => to_string(hostname),
70      "address_family" => family,
71      "addresses"      => Networking.format_addresses(addresses)
72    }}
73  end
74  def output({:ok, result}, _opts) do
75    addresses = hostent(result, :h_addr_list)
76    {:ok, Enum.join(Networking.format_addresses(addresses), "\n")}
77  end
78  use RabbitMQ.CLI.DefaultOutput
79
80  def usage() do
81    "resolve_hostname <hostname> [--address-family <ipv4 | ipv6>]"
82  end
83
84  def help_section(), do: :configuration
85
86  def description(), do: "Resolves a hostname to a set of addresses. Takes Erlang's inetrc file into account."
87
88  def banner([hostname], %{offline: false, node: node_name, address_family: family}) do
89    "Asking node #{node_name} to resolve hostname #{hostname} to #{family} addresses..."
90  end
91  def banner([hostname], %{offline: true, address_family: family}) do
92    "Resolving hostname #{hostname} to #{family} addresses..."
93  end
94end
95