mix new sensor --module Sensor
cd sensor
vi mix.exs
We have to add the wierl
and epcap
Erlang packages to the dependencies.
defp deps do
[{:wierl, github: "msantos/wierl"}, {:epcap, github: "msantos/epcap"}]
end
Then install the dependecies. Open a shell (it will compile wierl
and epcap
)
mix deps.get
iex -S mix
They are Erlang packages so the interoperability is subject to Erlang's rules. There is a good summary at https://elixirschool.com/lessons/advanced/erlang/#erlang-packages
At the time of writing (Nov 17 2016) there is an open issue (by me) on the pkt
internal dependency of epcap
. It crashes when it receives some packets apparently generated by the Rasperry PI 3 own WiFi card. You can turn it off if the PI is connected with a cable, otherwise look at msantos/pkt#40 and check how it evolved. The issue contains a fix that apparently works. You might have to apply it to the files in deps/pkt/
.
We need them to put the WiFi interface in monitor mode and capture packets.
References:
- https://github.com/msantos/wierl
- https://github.com/msantos/epcap
- http://blog.listincomprehension.com/2011/03/wireless-scanning-with-erlang.html
We need to give some capability to beam.smp
(the multiprocessor one), which is used by Erlang and Elixir on the Raspberry.
sudo setcap cap_net_admin=ep /usr/local/lib/erlang/erts-8.1/bin/beam.smp
and to remove the capability
sudo setcap -r /usr/local/lib/erlang/bin/erl
sudo setcap -r ~/elixir/bin/iex
These are some exercises in the Elixir interactive shell to get familiar with the key concepts of the setup of the WiFi card and packet capture.
Connect to the Raspberry with two shells. Be sure to use the IP address of the internal WiFi adapter or of the Ethernet card. Don't connect to the WiFi card that you're going to put in monitor mode: you'll get disconnected.
ssh pi@<address>
sudo apt-get install iw # you'll need this
cd /path/to/the/sensor/app
iex -S mix
interface = "wlan1"
Put the interface into monitor mode.
:ok = :wierl_config.down(interface)
{:ok, channel_def} = :wierl_config.param(interface, {:mode, :wierl.mode(:monitor)})
{:channel, channel_number} = :wierl.decode({:channel, channel_def})
:ok = :wierl_config.up(interface)
Use the other shell to verify that it worked.
$ iwconfig wlan1
wlan1 IEEE 802.11bgn Mode:Monitor Frequency:2.447 GHz Tx-Power=20 dBm
Retry short limit:7 RTS thr:off Fragment thr:off
Power Management:off
$ iw dev lan info
Interface wlan1
ifindex 4
wdev 0x100000001
addr <the card mac address>
type monitor
wiphy 1
channel 6 (2447 MHz), width: 20 MHz (no HT), center1: 2447 MHz
...
Put the interface back to managed mode.
:ok = :wierl_config.down(interface)
{:ok, channel_def} = :wierl_config.param(interface, {:mode, :wierl.mode(:infra)})
:ok = :wierl_config.up(interface)
and check that it's back to normal. It's going to get an IP address again after some time.
$ iwconfig wlan1
wlan1 IEEE 802.11bgn ESSID:"<your wifi network>"
Mode:Managed Frequency:2.447 GHz Access Point: <your AP mac address>
...
We could get packets with wierl
like this
{:ok, socket} = :wierl_monitor.open(interface)
{:ok, frame} = :wierl_monitor.read(socket)
:wierl_monitor.close(socket)
but we'll use epcap
for that.
To get packets with epcap
put wlan1
in monitor mode.
Finally receive
a packet and display the source and destination mac addresses. We'll need both of them.
interface = "wlan1"
:ok = :wierl_config.down(interface)
{:ok, channel_def} = :wierl_config.param(interface, {:mode, :wierl.mode(:monitor)})
{:channel, channel_number} = :wierl.decode({:channel, channel_def})
:ok = :wierl_config.up(interface)
{:ok, pid} = :epcap.start([{:interface, interface, :monitor, true}])
receive do
{:packet, data_link_type, _, _, packet} ->
case :pkt.decode(:pkt.dlt(data_link_type), packet) do
{:ok, decoded_packet} ->
{header, _} = decoded_packet
case hd(header) do
{:ether, source_mac_address, destination_mac_address, _, _} ->
IO.puts(Base.encode16(source_mac_address, case: :lower))
IO.puts(Base.encode16(destination_mac_address, case: :lower))
_ ->
IO.puts("unsupported")
end
{:error, _, _} ->
IO.puts("error")
end
_ -> :epcap.stop(epcap)
end
:epcap.stop(pid)
ok = :wierl_config.up("wlan0")
:ok = :wierl_config.down(interface)
{:ok, channel_def} = :wierl_config.param(interface, {:mode, :wierl.mode(:infra)})
:ok = :wierl_config.up(interface)
To change channel:
{:ok, channel_def} = :wierl_config.param(interface, {:freq, 6})
{:ok, channel_def} = :wierl_config.param(interface, {:freq, 7})
...
Check that it worked with
iw dev wlan1 info