# ExDhcp
**An instrumentable DHCP GenServer for Elixir**
_Largely inspired by [one_dhcpd][1]_
<img src="https://api.travis-ci.com/RstorLabs/ex_dhcp.svg?branch=master"/>
## General Description
ExDhcp is an instrumentable DHCP GenServer, with an opinionated interface that takes after the `GenStage` design. We couldn't use GPL licenced material in-house, so this project was derived from `one_dhcpcd`.
At the moment, unlike `one_dhcpcd`, it does not implement a full DHCP server, but you *could* use ExDhcp to implement that functionality. ExDhcp is ideal for using DHCP functionality for some other purpose, such as [PXE][2] booting.
If you would like to easily implement distributed DHCP with custom code hooks for custom functionality, ExDhcp might be for you.
## Usage Notes
A minimal DhcpServer implements the following three methods:
- `handle_discover`
- `handle_request`
- `handle_decline`
It might look something like this:
```elixir
defmodule MyDhcpServer do
use ExDhcp
alias ExDhcp.Packet
def start_link(init_state) do
ExDhcp.start_link(__MODULE__, init_state)
end
@impl true
def init(init_state), do: {:ok, init_state}
@impl true
def handle_discover(request, xid, mac, state) do
# insert code here. Should assign the unimplemented values
# for the response below:
response = Packet.respond(request, :offer,
yiaddr: issued_your_address,
siaddr: server_ip_address,
subnet_mask: subnet_mask,
routers: [router],
lease_time: lease_time,
server: server_ip_address,
domain_name_servers: [dns_server]))
{:respond, response, new_state}
end
@impl true
def handle_request(request, xid, mac, state) do
# insert code here
response = Packet.respond(request, :ack,
yiaddr: issued_your_address ...)
{:respond, response, state}
end
@impl true
def handle_decline(request, xid, mac, state) do
# insert code here
response = Packet.respond(request, :offer,
yiaddr: new_issued_address ...)
{:respond, response, state}
end
end
```
For more details, see the documentation.
### Deployment
The [DHCP protocol][3] listens in on port *67*, which is below the privileged port limit *(1024)* for most, e.g. Linux distributions.
ExDhcp doesn't presume that it will be running as root or have access to that port, and by default listens in to port *6767*. If you expect to have access to privileged ports, you can set the port number in the module configuration.
Alternatively, on most linux distributions you can use `iptables` to forward broadcast UDP from port *67* to port *6767* and vice versa. The following incantations will achieve this:
```bash
iptables -t nat -I PREROUTING -p udp --src 0.0.0.0 --dport 67 -j DNAT --to 0.0.0.0:6767
iptables -t nat -A POSTROUTING -p udp --sport 6767 -j SNAT --to <server ip address>:67
```
_NB: If you're using a port besides *6767*, be sure to replace it with your chosen port._
There may be situations where you would like to bind DHCP activity to a specific ethernet interface; this is settable from the module settings.
In order to successfully bind to the interface on Linux machines, do the following as superuser:
```bash
setcap cap_net_raw=ep /path/to/beam.smp
```
## TODOs
- [ ] publish to hex.pm
## Installation
If available in [Hex](https://hex.pm/docs/publish), the package can be installed
by adding `ex_dhcp` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:ex_dhcp, "~> 0.1.0"}
]
end
```
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) and published on [HexDocs](https://hexdocs.pm). Once published, the docs can be found at [https://hexdocs.pm/dhcp](https://hexdocs.pm/dhcp).
<!-- References -->
[1]: https://github.com/fhunleth/one_dhcpd
[2]: https://en.wikipedia.org/wiki/Preboot_Execution_Environment
[3]: https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol