Current section
Files
Jump to
Current section
Files
lib/dhcp_server.ex
defmodule DHCPServer do
@moduledoc """
Manages DHCP connections.
If NervesNetwork exists, it will manage bringing up and tearing down of
the interface.
"""
use Supervisor
@default [
authoritative: true,
lease_time: 3600,
gateway: "192.168.24.1",
netmask: "255.255.255.0",
range: {"192.168.24.2", "192.168.24.100"},
domain_servers: ["192.168.24.1"],
]
@doc """
Start a dhcp server.
# interface
* A linux network interface name.
# config
* `authoritative` - default: `#{@default[:authoritative]}`.
* `lease_file` - default: `"/var/run/dhcp_leases_<interface>.dets"`.
* `lease_time` - default: `#{@default[:lease_time]}`.
* `gateway` - default: `#{@default[:gateway]}`.
* `netmask` - default: `#{@default[:netmask]}`.
* `range` - default: `#{inspect @default[:range]}`.
* `domain_servers` - default: `#{inspect @default[:domain_servers]}`.
* `domain_name` - default: `"node()"`.
"""
def start_link(interface, config) do
Supervisor.start_link(__MODULE__, [interface, config], [name: :"#{__MODULE__}-#{interface}"])
end
@doc "stop a dhcp server."
def stop(interface) do
Supervisor.stop(:"#{__MODULE__}-#{interface}")
end
## Supervisor
def init([interface, config]) do
opts = config |> parse_config(interface)
{net_name_space, interface, server_id, next_server, lease_file, subnets, hosts} = :dhcp_server_config.parse_config(opts)
children = [
worker(DHCPServer.Server, [net_name_space, interface, server_id, next_server]),
worker(:dhcp_server_alloc, [interface, lease_file, subnets, hosts]),
worker(DHCPServer.Worker, [opts])
]
supervise(children, [strategy: :one_for_one])
end
## Private
defp parse_config(opts, interface) do
# Raise an argument error here if not supplied.
authoritative = get_config(opts, :authoritative)
lease_file = Keyword.get(opts, :lease_file, '/var/run/dhcp_leases_#{interface}.dets')
lease_time = get_config(opts, :lease_time)
gateway = get_config(opts, :gateway)
netmask = get_config(opts, :netmask)
{begin, fin} = get_config(opts, :range)
domain_servers = get_config(opts, :domain_servers)
domain_name = Keyword.get(opts, :domain_name, default_domain()) |> to_charlist
{a, b, c, _} = gateway = ip(gateway)
netmask = ip(netmask)
base = {a, b, c}
begin = validate_ip(begin, base)
fin = validate_ip(fin, base)
domain_servers = validate_ip(domain_servers, base)
# This is the format that the server expects.
# I don't know what the random numbers are.
[
{:server_id, gateway},
{:interface, interface},
{:authoritative, authoritative},
{:lease_file, lease_file},
{:netns, nil},
{:subnet,
{a, b, c, 0}, # Network
netmask, # Netmask
{begin,fin}, # Range
[
{1, netmask }, # Subnet Mask
{28, {a, b, c, 255} }, # Broadcast Address
{3, [gateway] }, # Routers
{15, domain_name }, # Domain Name
{6, domain_servers }, # Domain Name Servers
{51, lease_time } # Address Lease Time
]
}
]
end
defp get_config(opts, key) do
Keyword.get(opts, key, @default[key]) || raise ArgumentError, "Unsupplied key: #{key}"
end
defp ip(addr_bin) do
case String.split(addr_bin, ".") do
[a, b, c, d] -> {String.to_integer(a), String.to_integer(b), String.to_integer(c), String.to_integer(d)}
_ -> raise ArgumentError, "#{addr_bin} is not a valid ip address!"
end
end
defp validate_ip(ip_list, base) when is_list(ip_list) do
Enum.map(ip_list, &validate_ip(&1, base))
end
defp validate_ip(addr_bin, {base_a, base_b, base_c} = base) when is_binary(addr_bin) do
{addr_a, addr_b, addr_c, _addr_d} = ip_addr = ip(addr_bin)
if {addr_a, addr_b, addr_c} == base do
ip_addr
else
raise ArgumentError, "#{addr_bin} does not match base #{base_a}.#{base_b}.#{base_c}.x"
end
end
defp default_domain do
default = node() |> to_string()
case String.split(default, "@") do
[_, domain] -> "#{domain}.local"
[domain] -> "#{domain}.local"
end
end
end