Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ Runtime configuration is read from environment variables in `config/runtime.exs`
| `MN_NODE_EXECUTION_PROFILES` | Empty | Comma-separated execution profiles this runtime node may advertise after warmup. Empty means the node advertises no profiled executors. |
| `MN_NODE_CAPABILITIES` | Empty | Comma-separated runtime capabilities such as `video-codec:h264` or `ffmpeg`. |
| `MN_NODE_GPU` | Auto-detected | Optional override for whether this runtime node advertises GPU capacity. |
| `MN_CORE_HOST` | `localhost` | Host/IP used by the gRPC listener. |
| `MN_CORE_HOST` | `localhost` | Host/IP used by the gRPC listener. Empty, `localhost`, and invalid hostnames bind to `127.0.0.1`; use an IP literal such as `0.0.0.0` to listen on all interfaces. |
| `MN_GRPC_PORT` | `50051` | gRPC port. |
| `MN_API_ENABLED` | `true` | Enables API-related runtime config. |
| `MN_API_PORT` | `4000` | Core API config port. The separate `mn-api` package uses its own defaults. |
Expand Down
30 changes: 26 additions & 4 deletions lib/mirror_neuron/application.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
defmodule MirrorNeuron.Application do
use Application

require Logger

alias MirrorNeuron.Config

@impl true
Expand Down Expand Up @@ -73,11 +75,23 @@ defmodule MirrorNeuron.Application do
System.get_env("MN_NODE_ROLE", "runtime")
end

defp grpc_bind_opts(host) when host in ["", "localhost"] do
[ip: {127, 0, 0, 1}]
@doc false
def grpc_bind_opts(host) do
normalized_host =
host
|> to_string()
|> String.trim()

case normalized_host do
"" ->
[ip: {127, 0, 0, 1}]

host ->
parse_grpc_bind_host(host)
end
end

defp grpc_bind_opts(host) do
defp parse_grpc_bind_host(host) do
case :inet.parse_address(String.to_charlist(host)) do
{:ok, address} when tuple_size(address) == 4 ->
[ip: address]
Expand All @@ -86,7 +100,15 @@ defmodule MirrorNeuron.Application do
[net: :inet6, ip: address]

_ ->
[]
grpc_loopback_opts(host)
end
end

defp grpc_loopback_opts(host) do
unless String.downcase(host) == "localhost" do
Logger.warning("Invalid MN_CORE_HOST #{inspect(host)}; binding gRPC listener to 127.0.0.1")
end

[ip: {127, 0, 0, 1}]
end
end
25 changes: 25 additions & 0 deletions tests/unit/application_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule MirrorNeuron.ApplicationTest do
use ExUnit.Case, async: true

alias MirrorNeuron.Application

describe "grpc_bind_opts/1" do
test "binds empty and localhost values to IPv4 loopback" do
assert Application.grpc_bind_opts("") == [ip: {127, 0, 0, 1}]
assert Application.grpc_bind_opts("localhost") == [ip: {127, 0, 0, 1}]
assert Application.grpc_bind_opts("LOCALHOST") == [ip: {127, 0, 0, 1}]
assert Application.grpc_bind_opts(" localhost ") == [ip: {127, 0, 0, 1}]
end

test "passes through IP literal bind hosts" do
assert Application.grpc_bind_opts("0.0.0.0") == [ip: {0, 0, 0, 0}]
assert Application.grpc_bind_opts("127.0.0.1") == [ip: {127, 0, 0, 1}]
assert Application.grpc_bind_opts("::1") == [net: :inet6, ip: {0, 0, 0, 0, 0, 0, 0, 1}]
end

test "fails closed to IPv4 loopback for invalid bind hosts" do
assert Application.grpc_bind_opts("example.com") == [ip: {127, 0, 0, 1}]
assert Application.grpc_bind_opts("not an ip") == [ip: {127, 0, 0, 1}]
end
end
end
Loading