Fix to handler start_link fail

This commit is contained in:
Eishun Kondoh 2018-02-02 01:55:46 +09:00
parent 0df6336f5e
commit 658b7448da
17 changed files with 17 additions and 2012 deletions

View file

@ -4,14 +4,12 @@
Tres is a framework and set of helper libraries to develop OpenFlow controllers in Elixir.
the purpose of this project is to design and prototype an experimental network switch controller that implements the OpenFlow 1.3.x. The project explores the scalability and robustness of such controllers on a scale much larger than typically considered: 100,000s of endpoints, 10,000s of switches.
## Installation
```elixir
def deps do
[
{:tres, git: "https://gh.iiji.jp/Isono/tres", branch: "develop"}
{:tres, github: "shun159/tres", branch: "develop"}
]
end
```

View file

@ -1,20 +0,0 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez

View file

@ -1,21 +0,0 @@
# Heckle
**TODO: Add description**
## Installation
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
by adding `heckle` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:heckle, "~> 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/heckle](https://hexdocs.pm/heckle).

View file

@ -1,31 +0,0 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config
config :heckle,
vlan_tagging: true,
vlan_id: 123,
vlan_trunk1: "1",
vlan_trunk2: "4",
access_port1: "2",
access_port2: "3",
receiver_mac: "1a2c3d0e0191",
receiver_ip: {192,168,123,1},
sender_mac: "cec9bd531dfc",
inside_local: {192,168,11,1},
outside_local: {192,168,123,2},
flow_pattern: :nat # :nat || :bum
config :tres,
protocol: :tcp,
port: 6633,
max_connections: 10,
num_acceptors: 10,
callback_module: Heckle.Controller,
callback_args: ["0002b05ada98d790"]
config :logger,
level: :info,
format: "$date $time [$level] $metadata$message\n",
metadata: [:application],
handle_otp_reports: true

View file

@ -1,18 +0,0 @@
defmodule Heckle do
@moduledoc """
Documentation for Heckle.
"""
@doc """
Hello world.
## Examples
iex> Heckle.hello
:world
"""
def hello do
:world
end
end

View file

@ -1,20 +0,0 @@
defmodule Heckle.Application do
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
@moduledoc false
use Application
def start(_type, _args) do
# List all child processes to be supervised
children = [
# Starts a worker by calling: Heckle.Worker.start_link(arg)
# {Heckle.Worker, arg},
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: Heckle.Supervisor]
Supervisor.start_link(children, opts)
end
end

View file

@ -1,201 +0,0 @@
defmodule Heckle.Controller do
use GenServer
use Bitwise
use Tres.Controller
import Logger
alias Heckle.PipelineProfiles
alias Heckle.FlowPatterns
defmodule State do
defstruct [
dpid: nil,
conn_ref: nil,
access_port1_name: nil,
access_port2_name: nil,
trunk_port1_name: nil,
trunk_port2_name: nil,
access_port1: nil,
access_port2: nil,
trunk_port1: nil,
trunk_port2: nil,
vlan_tagging: nil,
vlan_id: nil,
receiver_mac: nil,
receiver_ip: nil,
sender_mac: nil,
inside_local: nil,
outside_local: nil,
flow_pattern: nil
]
end
def start_link(dpid, args) do
GenServer.start(__MODULE__, [dpid, args])
end
def init([{dpid, _aux_id}, [dpid]]) do
:ok = info("Switch Ready: dpid: #{inspect(dpid)} on #{inspect(self())}")
state = init_state(dpid)
{:ok, state}
end
def init([{dpid, _aux_id}, [_dpid]]) do
:ok = info("Switch Ready: dpid: #{inspect(dpid)} but not acceptable")
:ignore
end
def handle_cast(:send_flows, state) do
:ok = print_flows(state)
state
|> FlowPatterns.flows
|> Enum.each(&send_flow_mod_add(state.dpid, &1))
{:noreply, state}
end
def handle_info(%Desc.Reply{mfr_desc: "Aruba"} = desc, %State{dpid: dpid} = state) do
info("Switch Desc: mfr = #{desc.mfr_desc} hw = #{desc.hw_desc} sw = #{desc.sw_desc}")
:ok = PipelineProfiles.of_aruba()
|> TableFeatures.Request.new
|> send_message(dpid)
{:noreply, state}
end
def handle_info(%Desc.Reply{} = desc, state) do
:ok = info("Switch Desc: mfr = #{desc.mfr_desc} hw = #{desc.hw_desc} sw = #{desc.sw_desc}")
:ok = GenServer.cast(self(), :send_flows)
{:noreply, state}
end
def handle_info(%TableFeatures.Reply{xid: xid}, state) do
:ok = info("Pipeline modification is success (xid: #{xid})")
:ok = GenServer.cast(self(), :send_flows)
{:noreply, state}
end
def handle_info(%Flow.Reply{flows: flows}, state) do
for flow <- flows do
:ok = info(
"table_id: #{flow.table_id} " <>
"pkt_count: #{flow.packet_count} "<>
"byt_count: #{flow.byte_count} "<>
"match: #{inspect(flow.match)} "<>
"insts: #{inspect(flow.instructions)} "
)
end
{:noreply, state}
end
def handle_info(%PortDesc.Reply{ports: ports}, state) do
info("Received Port Desc")
access_port1 = Enum.find(ports, fn(port) -> port.name == state.access_port1_name end)
access_port2 = Enum.find(ports, fn(port) -> port.name == state.access_port2_name end)
trunk_port1 = Enum.find(ports, fn(port) -> port.name == state.trunk_port1_name end)
trunk_port2 = Enum.find(ports, fn(port) -> port.name == state.trunk_port2_name end)
:ok = desc_stats_request(state.dpid)
{:noreply, %{state|
access_port1: access_port1,
access_port2: access_port2,
trunk_port1: trunk_port1,
trunk_port2: trunk_port2}
}
end
def handle_info(%ErrorMsg{code: code, type: type, data: data, xid: xid}, state) do
:ok = warn("Request Failed(xid: #{xid}):"<>
" code: #{code}"<>
" type: #{type}"<>
" data: #{inspect(data)}"<>
" dpid: #{inspect(state.dpid)}")
{:stop, :request_failed, state}
end
def handle_info(%PacketIn{data: data}, state) do
:ok = warn("Table miss occured:"<>
" data: #{inspect(:pkt.decapsulate(data))}"<>
" dpid: #{inspect(state.dpid)}")
{:noreply, state}
end
def handle_info({:'DOWN', ref, :process, _pid, _reason}, %State{conn_ref: ref} = state) do
:ok = debug("Switch Disconnected: dpid: #{inspect(state.dpid)}")
{:stop, :normal, state}
end
def handle_info(info, state) do
:ok = info("Unhandled message: #{inspect(info)}")
{:noreply, state}
end
# private functions
defp init_state(dpid) do
:ok = init_datapath(dpid)
conn_ref = SwitchRegistry.monitor(dpid)
config = Application.get_all_env(:heckle)
%State{
dpid: dpid,
conn_ref: conn_ref,
access_port1_name: config[:access_port1],
access_port2_name: config[:access_port2],
trunk_port1_name: config[:vlan_trunk1],
trunk_port2_name: config[:vlan_trunk2],
vlan_tagging: config[:vlan_tagging] || true,
vlan_id: 0x1000 ||| (config[:vlan_id] || 0),
receiver_mac: config[:receiver_mac],
receiver_ip: config[:receiver_ip],
sender_mac: config[:sender_mac],
inside_local: config[:inside_local],
outside_local: config[:outside_local],
flow_pattern: config[:flow_pattern] || :nat
}
end
defp init_datapath(dpid) do
:ok = send_flow_mod_delete(dpid)
:ok = port_desc_stats_request(dpid)
:ok = set_config(dpid)
end
defp desc_stats_request(dpid) do
:ok = send_message(Desc.Request.new, dpid)
end
defp port_desc_stats_request(dpid) do
:ok = send_message(PortDesc.Request.new, dpid)
end
defp set_config(dpid) do
:ok = send_message(SetConfig.new(miss_send_len: :no_buffer), dpid)
end
defp print_flows(state) do
state
|> FlowPatterns.flows
|> Enum.each(&print_flow/1)
end
defp print_flow(flow_opts) do
flow_opts
|> FlowMod.new
|> Openflow.to_binary
|> binary_to_space_delimited_hex
|> ofp_print_cmd
|> Logger.info
end
defp ofp_print_cmd(print_args) do
{result, _code} = System.cmd("ovs-ofctl", ["ofp-print", "#{print_args}"])
result
end
defp binary_to_space_delimited_hex(binary) do
binary
|> split_to_hex_string
|> Enum.join(" ")
|> String.downcase
end
defp split_to_hex_string(binary) do
for <<int <- binary>>, do: integer_to_hex(int)
end
defp integer_to_hex(int) do
case Integer.to_string(int, 16) do
<<d>> -> <<48, d>>
dd -> dd
end
end
end

View file

@ -1,125 +0,0 @@
defmodule Heckle.FlowPatterns do
use Bitwise
use Tres.Controller
@mcast {"010000000000", "010000000000"}
def flows(%Heckle.Controller.State{flow_pattern: :nat} = state) do
[[table_id: 1,
priority: 30,
cookie: 0x3000000000000001,
match: Match.new(
eth_dst: state.receiver_mac,
eth_src: state.sender_mac,
eth_type: 0x0800,
ipv4_src: state.inside_local,
ipv4_dst: state.receiver_ip
),
instructions: [
ApplyActions.new([
SetField.new({:eth_dst, state.receiver_mac}),
SetField.new({:ipv4_src, state.outside_local}),
Output.new(state.trunk_port2.number)
])]
],
[table_id: 1,
priority: 30,
cookie: 0x3000000000000001,
match: Match.new(
eth_dst: state.sender_mac,
eth_src: state.receiver_mac,
eth_type: 0x0800,
vlan_vid: 123,
ipv4_src: state.receiver_ip,
ipv4_dst: state.outside_local
),
instructions: [
ApplyActions.new([
PopVlan.new,
SetField.new({:eth_src, state.receiver_mac}),
SetField.new({:eth_dst, state.sender_mac}),
SetField.new({:ipv4_dst, state.inside_local}),
Output.new(state.access_port1.number)
])]
],
] ++ classifier(state)
end
def flows(%Heckle.Controller.State{flow_pattern: :bum} = state) do
[[table_id: 1,
priority: 30,
cookie: 0x3000000000000001,
match: Match.new(
vlan_vid: state.vlan_id,
eth_dst: @mcast
),
instructions: [
ApplyActions.new([
Openflow.Action.Output.new(state.trunk_port1.number),
Openflow.Action.PopVlan.new,
Openflow.Action.Output.new(state.access_port1.number),
Openflow.Action.Output.new(state.access_port2.number)
])]
]] ++ classifier(state)
end
# private functions
defp classifier(state) do
[
[table_id: 0,
priority: 50,
cookie: 0x1000000000000001,
match: Match.new(
in_port: state.trunk_port1.number,
vlan_vid: state.vlan_id
),
instructions: [GotoTable.new(1)]
],
[table_id: 0,
priority: 50,
cookie: 0x1000000000000001,
match: Match.new(
in_port: state.trunk_port2.number,
vlan_vid: state.vlan_id
),
instructions: [GotoTable.new(1)]
],
[table_id: 0,
priority: 20,
cookie: 0x1000000000000001,
match: Match.new(
in_port: state.access_port1.number,
eth_src: state.sender_mac,
),
instructions: [
ApplyActions.new([
PushVlan.new,
SetField.new({:vlan_vid, state.vlan_id})
]),
GotoTable.new(1)
]
],
[table_id: 0,
priority: 20,
cookie: 0x1000000000000001,
match: Match.new(
in_port: state.access_port2.number,
eth_src: state.sender_mac,
),
instructions: [
ApplyActions.new([
PushVlan.new,
SetField.new({:vlan_vid, state.vlan_id})
]),
GotoTable.new(1)
]
],
[table_id: 0,
priority: 1,
match: Match.new,
cookie: 0x1000000000000001,
instructions: [ApplyActions.new(Output.new(:controller))]
],
]
end
end

View file

@ -1,32 +0,0 @@
defmodule Heckle.PipelineProfiles do
use Tres.Controller
def of_aruba do
[
TableFeatures.Body.new(
table_id: 0,
name: "classifier",
max_entries: 10,
config: [:table_miss_mask], # deprecated mask.
match: [:in_port, :vlan_vid, :eth_src],
wildcards: [:in_port, :vlan_vid, :eth_src],
instructions: [GotoTable, ApplyActions],
apply_actions: [Output, PushVlan, SetField, PopVlan],
apply_setfield: [:eth_dst, :vlan_vid],
next_tables: [1],
),
TableFeatures.Body.new(
table_id: 1,
name: "process",
max_entries: 10,
config: [:table_miss_mask],
match: [:eth_src, :masked_eth_dst, :vlan_vid, :eth_type, :ipv4_src, :ipv4_dst],
wildcards: [:eth_src, :masked_eth_dst, :vlan_vid, :eth_type, :ipv4_src, :ipv4_dst],
instructions: [ApplyActions],
apply_actions: [SetField, PopVlan, PushVlan, Output],
apply_setfield: [:eth_src, :eth_dst, :vlan_vid, :ipv4_src, :ipv4_dst],
next_tables: [],
)
]
end
end

View file

@ -1,23 +0,0 @@
defmodule Heckle.Mixfile do
use Mix.Project
@tres_path "../../../tres"
def project do
[app: :heckle,
version: "0.1.0",
elixir: "~> 1.5",
start_permanent: Mix.env == :prod,
deps: deps()]
end
def application do
[extra_applications: [:logger, :tres],
mod: {Heckle.Application, []}]
end
defp deps do
[{:tres, path: @tres_path},
{:pkt, github: "msantos/pkt", branch: "master"}]
end
end

View file

@ -1,6 +0,0 @@
%{"binpp": {:git, "https://github.com/jtendo/binpp.git", "64bd68d215d1a6cd35871e7c134d7fe2e46214ea", [branch: "master"]},
"eovsdb": {:git, "https://github.com/shun159/eovsdb.git", "1ff1572708d72fd25631c681f2102407903252a3", [branch: "master"]},
"jsone": {:git, "https://github.com/sile/jsone.git", "eecc9666c7165e1870b78a7a762549ae8d1c391b", [tag: "1.2.1"]},
"pkt": {:git, "https://github.com/msantos/pkt.git", "3afb1967f34324c1dec5035a6e36232da815c2e6", [branch: "master"]},
"ranch": {:hex, :ranch, "1.4.0", "10272f95da79340fa7e8774ba7930b901713d272905d0012b06ca6d994f8826b", [], [], "hexpm"},
"uuid": {:git, "https://github.com/avtobiff/erlang-uuid.git", "585c2474afb4a597ae8c8bf6d21e5a9c73f18e0b", [tag: "v0.5.0"]}}

View file

@ -1,8 +0,0 @@
defmodule HeckleTest do
use ExUnit.Case
doctest Heckle
test "greets the world" do
assert Heckle.hello() == :world
end
end

View file

@ -1 +0,0 @@
ExUnit.start()

View file

@ -124,15 +124,19 @@ defmodule Tres.SecureChannel do
defp init_handler(state_data) do
%State{datapath_id: dpid, aux_id: aux_id} = state_data
{:ok, pid} = MessageHandlerSup.start_child({dpid, aux_id})
case MessageHandlerSup.start_child({dpid, aux_id}) do
{:ok, pid} ->
ref = Process.monitor(pid)
%{state_data | handler_pid: pid, handler_ref: ref}
{:error, reason} ->
{:stop, reason}
end
end
# INIT state
defp handle_INIT(:enter, _old_state, state_data) do
debug(
"[#{__MODULE__}] Initiate HELLO handshake: " <> ">#{state_data.ip_addr}:#{state_data.port}"
"[#{__MODULE__}] Initiate HELLO handshake: " <> "#{state_data.ip_addr}:#{state_data.port}"
)
initiate_hello_handshake(state_data)
@ -202,9 +206,13 @@ defmodule Tres.SecureChannel do
# CONNECTED state
defp handle_CONNECTED(:enter, :CONNECTING, state_data) do
new_state_data = init_handler(state_data)
case init_handler(state_data) do
%State{} = new_state_data ->
start_periodic_idle_check()
{:keep_state, new_state_data}
{:stop, reason} ->
close_connection({:handler_down, reason}, state_data)
end
end
defp handle_CONNECTED(:info, :idle_check, state_data) do
@ -563,10 +571,8 @@ defmodule Tres.SecureChannel do
{:stop, :normal, %{state_data | socket: nil}}
end
defp close_connection({:handler_down = disconnected_reason, reason}, state_data) do
defp close_connection({:handler_down = _disconnected_reason, reason}, state_data) do
warn("[#{__MODULE__}] connection terminated: Handler process down by #{reason}")
%State{handler_pid: handler_pid} = state_data
send(handler_pid, {:switch_disconnected, disconnected_reason})
{:stop, :normal, %{state_data | socket: nil}}
end

View file

@ -1,250 +0,0 @@
defmodule Flay do
use GenServer
use Tres.Controller
import Logger
defmodule State do
defstruct datapath_id: nil,
tester_pid: nil,
conn_ref: nil,
reply_to: nil,
default_profile: nil
end
def start_link(datapath, args) do
GenServer.start_link(__MODULE__, [datapath, args], name: __MODULE__)
end
def init(args) do
state = init_controller(args)
GenServer.cast(Flay, :desc_stats)
GenServer.cast(Flay, :flow_del)
{:ok, state}
end
def handle_call(:port_desc_stats, from, state) do
send_message(PortDesc.Request.new(), state.datapath_id)
{:noreply, %{state | reply_to: from}}
end
def handle_call(:flow_stats, from, state) do
send_message(Flow.Request.new(), state.datapath_id)
{:noreply, %{state | reply_to: from}}
end
def handle_cast(:desc_stats, state) do
send_message(Desc.Request.new(), state.datapath_id)
{:noreply, state}
end
def handle_cast({:register_pid, tester_pid}, state) do
{:noreply, %{state | tester_pid: tester_pid}}
end
def handle_cast({:flow_install, flow_opts, tester_pid}, state) do
send_flow_mod_add(state.datapath_id, flow_opts)
flow_opts_to_ofp_print(flow_opts)
{:noreply, %{state | tester_pid: tester_pid}}
end
def handle_cast(:flow_del, state) do
send_flow_mod_delete(state.datapath_id)
{:noreply, state}
end
def handle_cast({:flow_del, cookie}, state) do
send_flow_mod_delete(state.datapath_id, cookie: cookie, cookie_mask: 0xFFFFFFFFFFFFFFFF)
{:noreply, state}
end
def handle_info(%ErrorMsg{} = error, state) do
send(state.tester_pid, error)
{:noreply, state}
end
def handle_info(%PacketIn{} = pktin, state) do
send(state.tester_pid, pktin)
{:noreply, state}
end
def handle_info(%TableFeatures.Reply{} = table, state) do
{:noreply, %{state | default_profile: table}}
end
def handle_info(%PortDesc.Reply{} = desc, state) do
GenServer.reply(state.reply_to, desc)
{:noreply, state}
end
def handle_info(%Desc.Reply{} = desc, state) do
info(
"[#{__MODULE__}] Switch Desc: " <>
"mfr = #{desc.mfr_desc} " <> "hw = #{desc.hw_desc} " <> "sw = #{desc.sw_desc} "
)
init_bridge(state.datapath_id, desc)
{:noreply, state}
end
def handle_info(%Flow.Reply{} = desc, state) do
GenServer.reply(state.reply_to, desc)
{:noreply, %{state | reply_to: nil}}
end
# `Catch all` function is required.
def handle_info(info, state) do
:ok = warn("[#{__MODULE__}] unhandled message #{inspect(info)}")
{:noreply, state}
end
# private functions
defp flow_opts_to_ofp_print(flow_opts) do
flow_opts
|> FlowMod.new()
|> Openflow.to_binary()
|> binary_to_space_delimited_hex
|> ofp_print_cmd
|> Logger.info()
end
defp ofp_print_cmd(print_args) do
{result, _code} = System.cmd("ovs-ofctl", ["ofp-print", "#{print_args}"])
result
end
defp binary_to_space_delimited_hex(binary) do
binary
|> split_to_hex_string
|> Enum.join(" ")
|> String.downcase()
end
defp split_to_hex_string(binary) do
for <<int <- binary>>, do: integer_to_hex(int)
end
defp integer_to_hex(int) do
case Integer.to_string(int, 16) do
<<d>> -> <<48, d>>
dd -> dd
end
end
defp init_controller([datapath_id, tester_pid]) do
conn_ref = SwitchRegistry.monitor(datapath_id)
%State{
datapath_id: datapath_id,
tester_pid: tester_pid,
conn_ref: conn_ref
}
end
defp init_bridge(datapath_id, %Desc.Reply{mfr_desc: "Aruba"}) do
:ok = info("Transform flow table pipeline")
tables = [
TableFeatures.Body.new(
table_id: 0,
name: "classifier",
max_entries: 50,
config: [:table_miss_mask],
match: [
:in_port,
:eth_type,
:eth_src,
:masked_eth_dst,
:ip_proto,
:vlan_vid,
:ipv4_src,
:udp_dst,
:tcp_dst
],
wildcards: [
:in_port,
:eth_src,
:eth_type,
:masked_eth_dst,
:vlan_vid,
:ip_proto,
:ipv4_src,
:udp_dst,
:tcp_dst
],
instructions: [
Openflow.Instruction.GotoTable,
Openflow.Instruction.ApplyActions
],
apply_actions: [
Openflow.Action.Output,
Openflow.Action.PushVlan,
Openflow.Action.PopVlan,
Openflow.Action.SetField
],
apply_setfield: [
:eth_dst,
:vlan_vid
],
next_tables: [
1
]
),
TableFeatures.Body.new(
table_id: 1,
name: "admission_control",
max_entries: 50,
config: [:table_miss_mask],
match: [
:eth_type,
:eth_src,
:masked_eth_dst,
:vlan_vid,
:ip_proto,
:udp_dst,
:tcp_dst,
:ipv4_src,
:ipv4_dst
],
wildcards: [
:eth_type,
:eth_src,
:masked_eth_dst,
:vlan_vid,
:ip_proto,
:udp_dst,
:tcp_dst,
:ipv4_src,
:ipv4_dst
],
instructions: [
Openflow.Instruction.GotoTable,
Openflow.Instruction.ApplyActions
],
apply_actions: [
Openflow.Action.Output,
Openflow.Action.PushVlan,
Openflow.Action.PopVlan,
Openflow.Action.SetField
],
apply_setfield: [
:eth_dst,
:vlan_vid,
:ipv4_src,
:ipv4_dst
]
)
]
TableFeatures.Request.new(tables)
|> send_message(datapath_id)
send_flow_mod_delete(datapath_id, table_id: :all)
end
defp init_bridge(_datapath_id, _mfr) do
:ok = info("Flow pipeline profile is not defined")
:ok
end
end

File diff suppressed because it is too large Load diff

View file

@ -1,77 +0,0 @@
defmodule Pf do
use GenServer
import Record
# Extract Erlang record for msantos/pkt
for {name, schema} <- extract_all(from_lib: "pkt/include/pkt.hrl") do
defrecord(name, schema)
end
defmodule State do
defstruct ifname: nil,
pcap_ref: nil,
tester_pid: nil
end
def inject!(pid, packet, payload \\ "") do
GenServer.cast(pid, {:inject, {packet, payload}})
end
def stop(pid) do
GenServer.cast(pid, :stop)
end
def start_link(ifname) do
ifname = String.to_charlist(ifname)
GenServer.start_link(__MODULE__, [ifname, self()])
end
def init([ifname, tester_pid]) do
{:ok, init_pf(ifname, tester_pid)}
end
def handle_cast({:inject, {headers, payload}}, state) do
headers_bin =
for header <- headers do
case header do
ether() -> :pkt.ether(header)
{:"802.1q", _, _, _, _} = vlan -> :pkt_802_1q.codec(vlan)
arp() -> :pkt.arp(header)
ipv4() -> :pkt.ipv4(header)
lldp() -> :pkt.lldp(header)
udp() -> :pkt.udp(header)
tcp() -> :pkt.tcp(header)
end
end
binary = Enum.join(headers_bin, "")
:epcap.send(state.pcap_ref, <<binary::bytes, payload::bytes>>)
{:noreply, state}
end
def handle_cast(:stop, state) do
{:stop, :normal, state}
end
def handle_cast(_req, state) do
{:noreply, state}
end
def handle_info({:packet, _dlt, _time, _len, data}, state) do
packet = :pkt.decapsulate(data)
packet_len = length(packet)
send(state.tester_pid, {to_string(state.ifname), Enum.take(packet, packet_len - 1)})
{:noreply, state}
end
def handle_info(_info, state) do
{:noreply, state}
end
# private functions
defp init_pf(ifname, tester_pid) do
{:ok, epcap_pid} = :epcap.start_link(interface: ifname, promiscuous: true, inject: true)
%State{pcap_ref: epcap_pid, ifname: ifname, tester_pid: tester_pid}
end
end